DEV Community

Cover image for Exploring Steganography in the Wild - Part 1
Retiago Drago
Retiago Drago

Posted on • Updated on

Exploring Steganography in the Wild - Part 1

Outlines

Introduction

Welcome to a fascinating exploration of steganography and its applications. Steganography is a technique often cloaked in mystery, yet it serves practical purposes that extend from cybersecurity to digital watermarking. In this blog post, we'll delve into the nitty-gritty details of how steganography is applied to digital images.

Steganography

Steganography is the practice of representing information within another message or physical object, in such a manner that the presence of the information is not evident to human inspection - Wikipedia.

In simpler terms, steganography is akin to embedding a covert message within another overt message—in our case, hiding one image within another.

Digital Images and Pixels

A digital image is an image composed of picture elements, also known as pixels, each with finite, discrete quantities of numeric representation for its intensity or gray level that is an output from its two-dimensional functions fed as input by its spatial coordinates denoted with x, y on the x-axis and y-axis, respectively - Wikipedia.

Imagine a digital image as an intricate mosaic composed of minuscule tiles, known as pixels. Each pixel contributes to the overall image by adding its own splash of color. The more pixels you have, the more vivid and detailed your image becomes.

Color Models

Color models are tools central to color theory that define the color spectrum based on presence or absence of a few primary colors - onlinelibrary.wiley.com

Think of color models as the recipe books for digital artists. Just like mixing primary colors in art class, color models help us understand how to blend different amounts of red, green, and blue light to produce various hues. In this discussion, we'll focus on the RGB (Red-Green-Blue) color model, which is the cornerstone for reproducing a wide array of colors in digital media.

The name of the model comes from the initials of the three additive primary colors, red, green, and blue. The main purpose of the RGB color model is for the sensing, representation, and display of images in electronic systems, such as televisions and computers - Wikipedia.

Binary Code

Binary code is a system of representing information using only two symbols, typically 0 and 1 - Britannica

In the realm of digital images, each pixel's color information is represented by 8-bit binary digits. This means each pixel can display one of (2^8) or 256 possible colors.

8 bit binary digit

In computing, the most significant bit (MSb) represents the highest-order place of a binary integer. Similarly, the least significant bit (LSb) is the bit position in a binary integer representing the binary 1s place of the integer - Wikipedia

The concept of most significant bit (MSb) and least significant bit (LSb) is crucial here. Altering the MSb can have a profound impact on a pixel's color, while changes to the LSb are usually subtle.

The leftmost bit is the most significant bit. We change this bit and it will affect tremendously on the value. For example, we flip the leftmost bit of the binary value of 165 from 1 to 0 (from 10100101 into 00100101) it will change the decimal value from 165 into 37.

On the other hand, the rightmost bit is the least significant bit. We change this bit and it won't affect tremendously on the value. For example, we flip the rightmost bit of the binary value of 165 from 1 to 0 (from 10100101 into 10100100) it will change the decimal value from 165 into 164.

pixels

So, let's tie it all together: each pixel in a digital image comprises three color values (RGB), each represented by an 8-bit binary digit. We'll exploit the "insignificance" of the least significant bits to embed one image into another without causing noticeable changes to the host image. This is the magic key to image-based steganography. We'll change the less significant bits of one image to include the most significant bits of another.

What To Expect

In this technical dev blog post, you'll find:

  • A deep dive into steganography, particularly the art of concealing one image within another.
  • A focus on LSB steganography, which involves manipulating the least significant bits of pixels in a cover image to hide another image.
  • Practical code examples and vivid visualizations to help you understand the process of hiding and retrieving images.
  • A discussion on the challenges and limitations one might encounter, such as the quality of the concealed image.

So, buckle up for an exciting journey through the world of steganography!

Project Setup

Before diving into the technicalities of steganography, it's essential to set up your workspace properly. This ensures that you can effortlessly follow along with the examples and get the most out of this exploration. Whether you're a seasoned coder or just dipping your toes into the Python ecosystem, I've got you covered.

I've created a Google Colab-compatible Jupyter Notebook to make this process seamless. The notebook is designed with a user-friendly UI/UX and hides the raw code, so you can focus on learning. And the best part? The code is pre-validated to run without hitches on the first try!

Open in Colab

If you're new to Google Colab or Python in general, don't worry! Here's an introductory video on Google Colab for Beginners to help you get started.

For those who are more experienced and might prefer to run the project locally, here are the Python package requirements to set up your environment:

Python 3.7.12
matplotlib==3.2.2
matplotlib-inline==0.1.3
matplotlib-venn==0.11.6
numpy==1.21.5
Pillow==7.1.2
scikit-learn==1.0.2
Enter fullscreen mode Exit fullscreen mode

Once you have your environment set up, you'll be ready to delve into the world of steganography with me!

Core Concepts

Mapping Techniques

Before diving into the nitty-gritty details, it's crucial to understand the four types of binary value mappings that we'll be using:

  1. Left-Half Bits Mapping (LHB Map)
    left half bits
    Think of this mapping as a way to extract the left-half bits values from the cover image within the merged image.

  2. Right-Half Bits Mapping (RHB Map)
    right half bits
    This mapping aims to extract the left-half bits values of the hidden image from the merged image.

  3. Whole Bits Mapping (MB Map)
    merged bits
    This mapping is utilized to construct the whole bits values of the merged image, using the left-half bits values from both the cover and hidden images.

  4. Possible Right-Half Bits Mapping (RRHB Map)
    reconstruction bits
    In this case, we'll randomly pick possible half bits values as we don't store the information about the half bits beforehand. The aim here is to construct the right-half bits values of the unmerged left-half bits image for both the unmerged cover and hidden images.

Bit Manipulation for Hiding Images

Now that we've understood the mapping types, let's look at how these mappings play a crucial role in hiding—or encoding—one image within another.

Encoding Process

encode flow

Here's a summarized flow of the encoding process:

  • Choose Images: Initially, you have to pick two images:
    • A "Cover Image," which will serve as the shell where your secret image will reside.
    • A "Hidden Image," which is the secret image you wish to hide within the cover image.
  • Extract and Map LHB (Left-Half Bits): The "Hidden Image" undergoes LHB (Left-Half Bits) Mapping. This extracts the left-half bits from the hidden image and maps them to create a new image called "Left-half bits Hidden Image."
  • Position the New Image: The "Left-half bits Hidden Image" is then positioned within the "Cover Image" according to specified criteria (e.g., upper left corner, lower right corner, etc.).
  • Merge: Finally, the "Cover Image" and the positioned "Left-half bits Hidden Image" are merged together. This forms the "Merged Image," which looks like your original cover image but has the hidden image embedded within it.

Bit Manipulation for Revealing Images

Let's shift our focus to how we can extract—or decode—the hidden image from the merged image.

Decoding Process

decode flow

Here's a quick rundown of the decoding process:

  • Use Merged Image: The initial step in decoding involves taking the "Merged Image," which contains both the "Cover Image" and the "Hidden Image."
  • Extract Bits: The "Merged Image" is then divided into its component "left-half bits" (LHB) and "right-half bits" (RHB). These are different portions of the image's pixel data.
  • Retrieve Images: The "left-half bits" are used to reconstruct an "Unmerged Left-half bits Cover Image," effectively extracting what used to be the cover image. Note that the right-half bits for this image are still zero. The "right-half bits" are used to reconstruct a "Unmerged Left-half bits Hidden Image," effectively extracting what used to be the hidden image, also with zero right-half bits. The position of this hidden image within the merged image is accounted for during this step.

Bit Manipulation for Reconstructing Images

Finally, let's look at how we can reconstruct the original images from their half-bit versions.

Reconstruction Process

reconstruction flow

Here's how the reconstruction process works:

  • Use Unmerged Bits: Use the unmerged left-half bits of both the cover and hidden images.
  • Map Right-Half Bits: Use the "RRHB Mapping" to generate possible right-half bits values randomly.
  • Reconstruct Images: The result is a reconstructed cover and hidden image.

I hope these explanations provide a clearer understanding of the steganography techniques involved in hiding, revealing, and reconstructing images.

Implementation Details

Supported Image Formats

In this exploration, we'll limit our focus to only two image formats: PNG and JPEG. This limitation helps keep our exploration straightforward.

Code Structure

Before diving into the code's functionality, it's essential to get a grasp of its structure. Below is a snippet that outlines the main components of the code.

.
├── get_bits_dict(start: int, end: int, recon: bool = False) -> Dict[int, int]
├── get_merged_bits_array() -> np.ndarray
├── dict_to_nparray(d: dict) -> np.ndarray
├── dict_to_2darray(d: dict) -> np.ndarray
└── Steganograph
    ├── Object Attributes
    │     ├── ispng: bool
    │     ├── original_cover_image: numpy array
    │     ├── original_hidden_image: numpy array
    │     ├── left_half_bits_hidden_image: numpy array, default None
    │     ├── merged_image: numpy array, default None
    │     ├── unmerged_left_half_bits_cover_image: numpy array, default None
    │     ├── unmerged_left_half_bits_hidden_image: numpy array, default None
    │     ├── reconstructed_cover_image: numpy array, default None
    │     └── reconstructed_hidden_image: numpy array, default None
    ├──── Methods
    │     ├── encode(pos: str = 'upper_left')
    │     ├── decode(pos: str = 'upper_left')
    │     ├── encode_decode(pos: str = 'upper_left')
    │     ├── reconstruct()
    │     ├── encode_decode_recon(pos: str = 'upper_left')
    │     ├── is_two_images_identical(opt: int = 0) -> bool
    │     ├── save_image(opt: int = 0)
    │     ├── plot_original()
    │     ├── plot_left_half_bits()
    │     ├── plot_merged_image()
    │     ├── plot_unmerged_left_half_bits()
    │     └── plot_recon()
    └──── Class Attributes
          ├── __lhb_lookup: lhb #Left half bits array lookup
          ├── __rhb_lookup: rhb #Right half bits array lookup
          ├── __mb_lookup: mb #Merged bits 2D array lookup
          ├── __rrhb_lookup: rrhb #Reconstruction right half bits dict lookup
          ├── __format: tuple ('jpeg', 'png')
          ├── __rgb: tuple ('RGB', 'Red', 'Green', 'Blue')
          └── __pos: tuple ('upper_left', 'upper_right', 'lower_left', 'lower_right')
Enter fullscreen mode Exit fullscreen mode

Explanation of Helper Functions

The code incorporates several helper functions tailored for the art of steganography. Here's a brief rundown:

  1. get_bits_dict: This function produces a dictionary that maps an integer (ranging from 0 to 255) to another integer. The mapping varies based on whether the process is for reconstruction. In the standard case, it extracts the left-half bits, whereas for reconstruction, it extracts the right-half bits.

  2. dict_to_nparray: Converting the dictionary from get_bits_dict into a numpy array facilitates faster lookup operations.

  3. get_merged_bits_array: This function constructs a 2D array where each cell at index [i][j] holds the merged left-half bits (cover image) of i and the left-half bits (hidden image) of j.

  4. dict_to_2darray: This works similarly to dict_to_nparray, but it reshapes the array into a 2D structure, particularly useful for image reconstruction.

  5. Respective function callings for later:

    • Generates a numpy array for quick look-up of the left-half bits of a given number.
    • Creates a numpy array for speedy retrieval of the right-half bits of a number.
    • Constructs a 2D numpy array for efficient look-up of the merged bits of two numbers.
    • Builds a 2D numpy array for quick reference of the right-half bits of a number during the reconstruction phase.

Explanation of Steganograph Class

Explanation of the __init__ Method:

The __init__ method initializes an instance of the Steganograph class.

Parameters:
  • cover_image_filepath: File path for the cover image (supports only JPEG or PNG formats).
  • hidden_image_filepath: File path for the hidden image (supports only JPEG or PNG formats).
Steps:
  1. Determine Image Formats: The method first determines the format (JPEG or PNG) of both the cover image and the hidden image using the imghdr.what() function. The formats are stored in format_ci and format_hi variables.

  2. Validation: It checks whether both images have valid formats. If the formats don't match any in the class' __format attribute, it raises a TypeError.

  3. Check Format Uniformity: It checks if both images have the same format. If not, it raises a TypeError.

  4. PNG Format Flag: Sets the ispng flag to True if the format is PNG; otherwise, sets it to False.

  5. Read Images: Reads the images into original_cover_image and original_hidden_image using the plt.imread() function from Matplotlib.

  6. Invoke read_and_adjust_images: Calls the method read_and_adjust_images() to perform additional adjustments on the images.

  7. Set Initial States: Initializes the other attributes to None, as these would be populated by other methods later.

Explanation of the read_and_adjust_images Method:

This method adjusts the read images for further processing, especially for PNG images. By the end of these methods, the class instance is well-prepared with loaded and adjusted images, and is ready for encoding and decoding operations.

Steps:
  1. Check if PNG: If the images are in PNG format (ispng is True):
    • Ignore Alpha Channel: If the images have an alpha (A) channel, it ignores it for now.
    • Normalization: PNG images are often read in a normalized format where the pixel values are floats between 0 and 1. This method multiplies them by 255 and rounds off to convert them to integers.
    • Type Casting: It then casts the numpy arrays to 'uint8' type, ensuring that they are 8-bit unsigned integers.

Explanation of the encode Method:

The encode method is responsible for creating a simple steganograph—merging a hidden image into a cover image in a way that it can later be extracted.

Parameters:
  • pos: Specifies where the hidden image will be located in relation to the cover image. Default is 'upper_left'.
Steps:
  1. Position Validation: It validates whether the given position pos is one of the four pre-defined positions (upper_left, upper_right, lower_left, lower_right). If not, it raises a ValueError.

  2. Get Left Half Bits: It calls get_left_half_bits to extract the left half-bits of the hidden image and stores them in the left_half_bits_hidden_image attribute.

  3. Merge Half Bits: It then calls merge_two_half_bits to merge the modified hidden image with the cover image.

Explanation of the get_left_half_bits Method:
Parameters:
  • img_arr: The image array that you want to extract the left half-bits from.
Steps:
  1. Lookup: It looks up the left half-bits of the image array img_arr using a pre-defined lookup table __lhb_lookup.

  2. Return: It returns the left half-bits.

Explanation of the merge_two_half_bits Method:
Parameters:
  • pos: The position where the hidden image will be located in relation to the cover image.
Steps:
  1. Copy Original Cover Image: It first creates a copy of the original cover image and stores it in self.merged_image.

  2. Get Slicing Range: It calls the get_slicing method to determine the slice where the hidden image will be placed.

  3. Merge the Half Bits: It merges the cover image and the modified hidden image together to create the final merged image (steganograph). It does this by using a pre-defined 2D lookup table __mb_lookup for each channel (RGB).

  4. Type Casting: Finally, it casts the merged_image into 'uint8' type.

Explanation of the get_slicing Method:
Parameters:
  • large_shape: A tuple representing the shape of the larger array, which is the cover image in this case.
  • small_shape: A tuple representing the shape of the smaller array, which is the hidden image.
  • pos: The position where the hidden image will be located in relation to the cover image. Valid options are 'upper_left', 'upper_right', 'lower_left', and 'lower_right'.
Steps:
  1. Extract Shape Information: The method first extracts the shape information of both the larger and smaller arrays. This information includes the number of rows and columns in each array.

  2. Calculate Slicing Range: Based on the pos value, the function calculates the range in the larger array where the smaller array will be placed. This is done using numpy slice notation (np.s_).

  3. Error Handling: If an invalid pos value is provided, a ValueError is raised.

  4. Return: The function returns the calculated slicing range, which can be used as a slice object for numpy arrays. This slice object will then be used to place the hidden image in the specified position within the cover image.

By the end of the encode method, you'll have a steganograph where the hidden image is merged into the cover image at the specified position.

Explanation of the decode Method:

The decode method aims to break down the steganograph created by the encode method to recover the original hidden and cover images.

Parameters:
  • pos: This parameter specifies where the hidden image was embedded within the cover image. The default is 'upper_left'.
Steps:
  1. Validation: First, the method checks if self.merged_image is None. If it is, it raises a TypeError. It also validates that the position pos is one of the acceptable positions. If not, a ValueError is raised.

  2. Unmerge: It calls the unmerge_two_half_bits method to separate the cover and hidden images from the merged image.

Explanation of the unmerge_two_half_bits Method:
Parameters:
  • pos: Specifies the original position where the hidden image was embedded within the cover image.
Steps:
  1. Slice Definition: Calls the get_slicing method to determine the slice range based on the pos value, i.e., where exactly the hidden image is located in the merged image.

  2. Extract Left Half Bits of Cover Image: Uses the get_left_half_bits method to extract the left half bits of the merged image and assigns them to self.unmerged_left_half_bits_cover_image.

  3. Extract Right Half Bits of Hidden Image: Similarly, it calls the get_right_half_bits method to extract the right half bits of the merged image. However, it does so only for the area specified by the slicing range returned by get_slicing.

Explanation of the get_right_half_bits Method:
Parameters:
  • img_arr: The image array that you want to extract the right half-bits from.
Steps:
  1. Lookup: It looks up the right half-bits of the image array img_arr using a pre-defined lookup table __rhb_lookup.

  2. Return: It returns the right half-bits.

In summary, the decode method and its supporting methods work together to reverse the steganographic process, extracting the hidden image and cover image from the merged image.

Explanation of the reconstruct Method:

The reconstruct method aims to restore the original cover and hidden images from their left-half bits images, which were previously separated during the decoding process.

Steps:
  1. Validation: The method checks that self.unmerged_left_half_bits_cover_image and self.unmerged_left_half_bits_hidden_image are not None. If either of them is None, it raises a TypeError, prompting you to run the decode() method first.

  2. Reconstruct Cover Image: The reconstruct_right_half_bits method is called to reconstruct the right-half bits of the cover image. The reconstructed image is stored in self.reconstructed_cover_image.

  3. Reconstruct Hidden Image: Similarly, the reconstruct_right_half_bits method is used to reconstruct the right-half bits of the hidden image. The reconstructed image is stored in self.reconstructed_hidden_image.

Explanation of the reconstruct_right_half_bits Method:
Parameters:
  • im_arr: The left-half bits of an image as a numpy array.
  • seed: A seed for the random number generator, defaulting to 1999.
Steps:
  1. Random Seed: The np.random.seed function is used to set the random seed to ensure that the random process is reproducible.

  2. Flattening Image: The ravel() method is used to flatten the im_arr, essentially turning it into a one-dimensional array (flat_im_arr).

  3. Random Indices: A random set of indices (random_indices) is generated using np.random.randint, with the size of the array based on the shape of flat_im_arr. This will simulate the random selection of right-half bits for each corresponding left-half bit.

  4. Reconstruction: The lookup table __rrhb_lookup is used in conjunction with random_indices to get a set of right-half bits for each corresponding left-half bit in flat_im_arr. The result (reconstructed_flat) is a one-dimensional array containing the reconstructed right-half bits.

  5. Reshaping: The reconstructed one-dimensional array is reshaped back into its original shape (shape) to form the reconstructed image.

  6. Type Casting: Finally, the numpy array is cast to the type 'uint8'.

  7. Return: The reconstructed image, now having both left and right half-bits, is returned.

In summary, the reconstruct method and its sub-method reconstruct_right_half_bits work together to restore the original cover and hidden images from their left-half bits versions. This final step completes the round-trip process of encoding, decoding, and reconstructing the images.

For the complete code and a more detailed walkthrough, check out the Jupyter Notebook linked below:

Open in Colab

Results and Visualization

Having delved into the theory and implementation details, it's time to see our steganography technique in action. We'll examine the visual changes and quality metrics at each stage of the process—encoding, decoding, and reconstruction.

Visual Examples of Pre-Encoded Images

First, let's consider our "Cover Image" and "Hidden Image." The image below serves as our cover:

Attack Titan.jpeg as cover image

Next, we have the hidden image:

Eren.jpeg as hidden image

For a deeper understanding, let's examine the RGB plot of these images. Click on the image below to enlarge it.

RGB plot of cover and hidden images

Visual Examples of Encoded Images

After encoding, we obtain two new images: the "Left Half Bits Hidden Image" and the "Merged Image."

Left Half Bits Hidden Image

The encoded "Left Half Bits Hidden Image" noticeably loses some quality compared to the original hidden image. For instance, the gradients do not blend as smoothly.

LHB of hidden image

Here's the RGB plot for a more in-depth analysis:

RGB plot of left half bits hidden image

Merged Image

At first glance, the "Merged Image" looks almost identical to the original cover image. However, a keen eye might spot a subtle face formation in the upper-left corner.

Merged image

For a more analytical view, let's check the RGB plot:

RGB plot of merged image

Visual Examples of Decoded Images

Next, we decode the merged image to obtain the "Unmerged Left Half Bits Cover Image" and the "Unmerged Left Half Bits Hidden Image." Both of these images show a loss in quality and coherence, similar to what we observed with the left half bits hidden image.

Unmerged left half bits cover image

Unmerged left half bits hidden image

Take a look at the RGB breakdown for further insights:

RGB plot of unmerged images

Visual Examples of Reconstructed Images

Finally, we move on to the reconstruction stage, where we aim to approximate the original images as closely as possible.

Reconstructed Cover Image

When compared to the unmerged version, the reconstructed cover image appears slightly brighter. However, it's essential to note that the image quality is still not on par with the original. Gradients, for example, do not blend as smoothly as they should.

Reconstructed cover image

Reconstructed Hidden Image

A similar trend is observed in the reconstructed hidden image. Though somewhat brighter than the unmerged version, the quality still falls short of the original.

Reconstructed hidden image

RGB Breakdown of Reconstructed Images

For a more analytical perspective, let's examine the RGB plots of these reconstructed images:

RGB plot of reconstructed images

Evaluation Metrics

For the evaluation, I utilized the Root Mean Square Error (RMSE) and Mean Absolute Error (MAE) metrics. These metrics help to quantify the differences between the images at different stages of the steganography process. While I can't speak to their application in formal steganalysis, they do offer a way to gauge the effectiveness of the encoding and decoding steps. If you have insights or recommendations on other metrics that might be more suitable, especially from a steganalysis point of view, feel free to share.

For instance, comparing the "Left Half Bits Hidden Image" with the original hidden image yielded the following results:

RMSE    : 8.868079206151936
MAE     : 7.579809053497942
Enter fullscreen mode Exit fullscreen mode

This indicates that the two images are indeed different. However, when comparing the "Left Half Bits Hidden Image" with the "Unmerged Left Half Bits Hidden Image," the result was:

RMSE    : 0.0
MAE     : 0.0
Enter fullscreen mode Exit fullscreen mode

This confirms that the encoding and decoding processes were executed correctly, as the two images are logically the same.

Once again, for the complete code and walkthrough, just run the Jupyter Notebook linked below:

Open in Colab

This concludes the "Results and Visualization" section, where we've explored the visual aspects and quality metrics of images at every stage—encoding, decoding, and reconstruction. These visual examples and metrics serve as a comprehensive evaluation of our steganography technique.

Limitations and Future Work

In this section, we delve into the limitations of the current implementation and discuss opportunities for future enhancements.

  1. Image Channel Handling: The code is designed to work specifically with RGB color spaces. This makes it less versatile when dealing with other color spaces or grayscale images.

  2. Limited File Format Support: The code currently supports only PNG and JPEG formats, potentially excluding users with images in other formats.

  3. Code Complexity: The codebase is somewhat intricate, which might make it less accessible for individuals who are not familiar with the project's specifics.

  4. Error Handling: While some error-handling mechanisms exist, they may not be comprehensive enough to address all potential edge cases.

  5. Hard-Coded Values: The presence of hard-coded values in the code reduces its flexibility and adaptability.

  6. Google Colab Dependency: The code relies on Google Colab-specific libraries, making it less portable for users who may prefer other development environments.

  7. Lack of Modularization: The code is not modular, which could complicate future efforts to extend its functionality or maintain it.

Conclusion

Summing Up the Journey

  • Enlightening and Challenging: Our exploration into steganography has been a revealing journey, showcasing both the potential and the challenges of this field.

  • Practical Applications: We demonstrated how one image could be hidden within another, a technique with various real-world applications such as digital watermarking and secure communications.

  • Identified Limitations: Like any scientific endeavor, our work has its constraints, from the limitations in color channel handling to dependencies on specific platforms like Google Colab. These offer avenues for future research and refinement.

Technical Achievements

  • Bit Manipulation: One of the technical milestones of this project is the successful implementation of bit manipulation techniques to encode and decode images.

  • Visual Proof: We provided visual examples to demonstrate our techniques in action. While the hidden image is not perfectly concealed in our experiments, we believe that in scenarios with more detailed and crowded cover images, the concealment could be more effective.

  • Quantitative Analysis: We've used metrics such as RMSE and MAE to measure differences between original and manipulated images. While these metrics offer insights, they may not be specifically tailored for steganalysis. If you have expertise in steganalysis, your feedback in the comments would be highly valuable.

Key Takeaways

The Complexity of Simplicity

What seemed like a straightforward task—hiding one image within another—unfolded into a labyrinth of complexities. The project has underscored the intricacies of image manipulation and bit-level operations.

Robustness and Flexibility are Paramount

The limitations we've faced have highlighted the importance of robust and flexible code. As we continue to refine this project, the lessons learned here will be invaluable.

Continuous Learning and Improvement

The world of steganography is vast, and there's so much more to explore. Your feedback and contributions are not just welcome but are essential for the continuous improvement of this project.

The Future is Open

The avenues for future work are promising. They range from refining the current code to make it more versatile to exploring more advanced steganographic techniques. With ongoing research and collaboration, the sky is the limit for what can be achieved.

Additional Sections

  • Code Repository: You can find the complete code and notebooks on GitHub.

GitHub logo ranggakd / steganography

Exploring Steganography In The Wild

Top comments (8)

Collapse
 
po0q profile image
pO0q 🦄

very nice!

Collapse
 
ranggakd profile image
Retiago Drago

Thank you hope it helps. Please do share it to your peers 😁

Collapse
 
taikedz profile image
Tai Kedzierski

Substituting Msbs of the covert image in the Lsbs of the lesser image is simple and brilliant. Not quite sure how the smoothing then worked (to remove the slight ghosted appearance of the covert image) ; and the RGBs, to this untrained eye, all look fairly identical...

This is an impressive and comprehensive breakdown of the technique. I admit I haven't digested it all , but nicely done. Wow.

Collapse
 
ranggakd profile image
Retiago Drago

Thank you for your kind words! I'm glad you found the post comprehensive and helpful. Steganography is indeed a fascinating field. If you have any specific questions or areas you'd like me to delve deeper into in my next post, feel free to let me know! I'm here to help. 😊

In the future I want to explore it from different angles.

Collapse
 
aatmaj profile image
Aatmaj

Very nice blog. stepwise breakdown with code is excellent!
glad to have learnt a new thing today😇

Collapse
 
ranggakd profile image
Retiago Drago

I'm thrilled to hear that you found the content valuable! If you believe others would benefit from this information on steganography as well, please feel free to share it. Spreading knowledge helps us all grow and learn. Remember, "Sharing is caring!" 😊

Collapse
 
scorcism profile image
Abhishek Pathak

great buddy. Its aweome

Collapse
 
ranggakd profile image
Retiago Drago

Absolutely! It's wonderful to see that you found the information useful. Knowledge is meant to be shared, and by sharing this post, you're contributing to a broader understanding of steganography. So go ahead, share away! You never know who might find it fascinating or how it could inspire someone. 😊