BLOG.CSHARPHELPER.COM: Use steganography to hide one picture inside another in C#
Use steganography to hide one picture inside another in C#
The example Use steganography to hide encrypted messages in an image in C# hides message bits in the least-significant color bits of randomly selected points within an image. The idea is that small changes to the least significant bits of a color won't be noticeable. For example, if a pixel's red color component is 254 instead of 255, no one will notice.
In fact, you can make a whole lot more changes without seriously altering an image.
To see why, notice that the most significant bit in an 8-bit binary value contributes fully half of the total value. If you change that bit, you change the value by 128, half of the possible number of values 256.
The next most significant bit accounts for half of the value that you can create with the remaining 7 bits. Changing that bit alters the value by 64, half of the number of possible values with 7 bits 128.
Together the two most significant bits account for 3/4 of the total value.
If you continue this line of reasoning, the 3 most significant bits account for roughly 7/8 of the total, the 4 most significant bits account for 15/16 of the total, and so forth. Even if you take away 4 of a pixel's 8 bits of red, green, and blue color information, the resulting color is pretty close to the original color. Instead of storing only 1 bit of information in pixels scattered around the image, you can store several bits in every red, green, and blue color value throughout the image.
This example does just that. It takes away the least significant pixels from one image and uses them to store the most significant pixels of a second hidden image. The hidden image's values are stored in the result image's least significant bits so they don't add greatly to the resulting combined image.
For example, suppose you want to use 3 bits to hide one image inside another and consider the red component of a particular pixel. Suppose the visible image's red component for that pixel in binary is 10110101 and the hidden image's red component for that pixel is 01010011. To use 3 bits to hide the second value inside the value, we remove the 3 least significant bits of the first value and replace them with the most significant bits of the second value. In this example, 10110101 + 01010011 = 10110010.
To see that the change is small, note that the original pixel's value was 10110101 = 181 and the final value is 10110010 = 178. We stored 3 bits from the hidden value but only changed the original value by a small amount.
To recover the hidden image, you extract the combined image's 3 least significant bits and use them for the hidden image's most significant bits. In this example, 10110010 gives the original image's value as 10110000 and the hidden image's value is 01000000. These values are slightly different from the original values but they're close enough to be useful.
The following code shows how the example program hides one image inside another.
// Hide bm_hidden inside bm_visible and return the result.
public static Bitmap HideImage(Bitmap bm_visible, Bitmap bm_hidden, int hidden_bits)
{
int shift = (8 - hidden_bits);
int visible_mask = 0xFF << hidden_bits;
int hidden_mask = 0xFF >> shift;
Bitmap bm_combined = new Bitmap(bm_visible.Width, bm_visible.Height);
for (int x = 0; x < bm_visible.Width; x++)
{
for (int y = 0; y < bm_visible.Height; y++)
{
Color clr_visible = bm_visible.GetPixel(x, y);
Color clr_hidden = bm_hidden.GetPixel(x, y);
int r = (clr_visible.R & visible_mask) + ((clr_hidden.R >> shift) & hidden_mask);
int g = (clr_visible.G & visible_mask) + ((clr_hidden.G >> shift) & hidden_mask);
int b = (clr_visible.B & visible_mask) + ((clr_hidden.B >> shift) & hidden_mask);
bm_combined.SetPixel(x, y, Color.FromArgb(255, r, g, b));
}
}
return bm_combined;
}
The code first makes bit masks to extract the bits from the visible and hidden images. It creates a new bitmap to store the result and then loops over the images' pixels. For each pixel, the code gets the color component values for the visible and hidden pixels. It uses the masks to clear the unwanted bits from each value and then combines them, shifting the hidden value's bits so they move into the least significant bit positions.
The following code shows how the program recovers an image hidden inside another image.
// Recover a hidden image.
public static Bitmap RecoverImage(Bitmap bm_combined, int hidden_bits)
{
int shift = (8 - hidden_bits);
int hidden_mask = 0xFF >> shift;
Bitmap bm_hidden = new Bitmap(bm_combined.Width, bm_combined.Height);
for (int x = 0; x < bm_combined.Width; x++)
{
for (int y = 0; y < bm_combined.Height; y++)
{
Color clr_combined = bm_combined.GetPixel(x, y);
int r = (clr_combined.R & hidden_mask) << shift;
int g = (clr_combined.G & hidden_mask) << shift;
int b = (clr_combined.B & hidden_mask) << shift;
bm_hidden.SetPixel(x, y, Color.FromArgb(255, r, g, b));
}
}
return bm_hidden;
}
This code loops over the combined image's pixels. It pulls the least significant bits out of each pixel's color components and shifts them so they become the most significant bits in the recovered image's pixels.
The following code shows how the program uses these methods.
// Hide and then recover the image.
private void btnGo_Click(object sender, EventArgs e)
{
Cursor = Cursors.WaitCursor;
int num_bits = (int)nudHiddenBits.Value;
// Hide the image.
picCombined.Image = Stego.HideImage(
(Bitmap)picVisible.Image,
(Bitmap)picHidden.Image,
num_bits);
// Recover the hidden image.
picRecovered.Image = Stego.RecoverImage(
(Bitmap)picCombined.Image, num_bits);
Cursor = Cursors.Default;
}
This code gets the number of bits to shift from the user's selection. It then calls the HideImage method to hide the image in the picHidden PictureBox inside the image displayed by the picVisible PictureBox and displays the result.
Next the code calls RecoverImage to extract the hidden image and displays the result.
If you look closely at the picture shown here, you'll see that the combined image looks very much like the original image and the recovered hidden image looks very much like its original value. By using 4 bits, you get a pretty remarkable result. If you experiment with other numbers of bits such as 1 or 7, you'll see lots of degradation in one image or the other.
This method works well for the photographs used here. It might not work as well for large geometric shapes of constant colors. For example, if you hid a series of vertical stripes inside an image containing a large rectangle, you might see the stripes showing through.
Comments