50. Steganography

Hiding data in an image's least significant bits and then later recovering the data is simple in principle, but it's complicated by the way images store pixel values. A typical 32-bit color image uses 8 bits to store each of a pixel's red, green, blue, and alpha (transparency) color components. We won't use the alpha component because that might cause a noticeable change when parts of the image become slightly transparent. That means we can store three bits of data per pixel.

Unfortunately, data is usually stored in bytes, so a byte of data might start in one pixel and end partway through another pixel.

An alternative storage strategy would be to use three pixels (giving nine bits) to store each 8-bit byte and just ignore the extra unused bit in each group of three pixels. Even that is complicated, however, because a byte might start in one row of pixels and end in the next row.

As long as things are going to be somewhat complicated anyway, I'm going to use every pixel instead of the three-pixels-per-byte strategy.

This isn't really a matter of trying to squeeze every last bit of data out of the image. That may be a concern for some very large messages, but images can store a surprisingly large amount of data. For example, a small 300 × 300 pixel image can hold 300 × 300 × 3 / 8 bytes or roughly 33 KB of data. A larger 1,024 × 1,024 pixel image can hold more than a third of a megabyte of data.

It's actually pretty remarkable how much data you can store in an image without it being obvious. I've written other programs that store the four high-order bits of one image in the four low-order bits of another image and the result still looks good.

The general approach is to write a method that stores one bit of information in one of a pixel's red, green, or blue components. We then write another method that uses the first one to save a byte of data. Next, we write a method that uses the byte method to save an array of bytes. Finally, we write a method that uses the byte array method to store a string or other piece of data. To make it all work, each of the methods must keep track of the row, column, and color component of the pixel that we are using to hold data, so later calls to the methods know where to place future data.

The following code shows the example solution's bit-level method:

// The component where a bit should be stored.
private enum RGB { R, G, B }

// Hide a single bit in the image.
// The bit parameter should be true to set the bit, false to clear it.
private static void StegBit(Bitmap bm, bool bit,
ref int x, ref int y, ref RGB component)
{
Color color = bm.GetPixel(x, y);
byte r = color.R;
byte g = color.G;
byte b = color.B;
byte a = color.A;
if (component == RGB.R)
{
if (bit) r |= 0b00000001;
else r &= 0b11111110;
color = Color.FromArgb(a, r, g, b);
bm.SetPixel(x, y, color);
component = RGB.G;
}
else if (component == RGB.G)
{
if (bit) g |= 0b00000001;
else g &= 0b11111110;
color = Color.FromArgb(a, r, g, b);
bm.SetPixel(x, y, color);
component = RGB.B;
}
else
{
if (bit) b |= 0b00000001;
else b &= 0b11111110;
color = Color.FromArgb(a, r, g, b);
bm.SetPixel(x, y, color);
component = RGB.R;
if (++x >= bm.Width)
{
// Move to the next row.
x = 0;
y++;
}
}
}

The RGB enumeration indicates a red, green, or blue color component. The StegBit method takes as parameters the bitmap where we are storing data, whether the bit should be set or cleared, the pixel's X and Y location, and the color component to use. Notice that the x, y, and component parameters are passed by reference so the method can update them.

The code first gets the color components of the pixel at position (x, y). It then takes similar actions for each of the possible color components. If the bit should be set, the code uses the OR operator (|) to combine the component's current value with the binary mask 00000001. That sets the least significant bit to 1 and leaves the other bits unchanged.

If the bit should be cleared, the code uses the AND operator (&) to combine the component's current value with the binary mask 11111110. That sets the least significant bit to 0 and leaves the other bits unchanged.

After calculating the new color component, the code updates the pixel's color value. Next, if the code is storing information in the pixel's red or green color component, the code advances the component property to the next color component so the next bit of data will be stored in that component.

If the method is using the blue component, the code increments x to move to the next pixel in the row. If this was the last pixel in the row, the code resets x to 0 and increments y to move to the beginning of the next rows of pixels.

From here, things get easier. The following code shows the example solution's byte-level method:

// Hide a byte in the image.
private static void StegByte(Bitmap bm, byte aByte,
ref int x, ref int y, ref RGB component)
{
byte mask = 0b00000001;
for (int i = 0; i < 8; i++)
{
bool setBit = (aByte & mask) != 0;
StegBit(bm, setBit, ref x, ref y, ref component);
mask <<= 1;
}
}

This method loops through a byte's bits. It uses a mask to figure out whether each bit should be set and calls the earlier StegBit method to set or clear the next bit in the image.

The following code shows the example solution's byte array-level method:

// Hide an array of bytes in the image.
private static void StegBytes(Bitmap bm, byte[] bytes,
ref int x, ref int y, ref RGB component)
{
foreach (byte aByte in bytes)
StegByte(bm, aByte, ref x, ref y, ref component);
}

This method simply loops through the bytes in the array and calls the earlier StegByte method to store each byte in the image.

Finally, the following method stores a string in an image:

// Return a copy of the bitmap with data embedded in it.
public static Bitmap StegMessage(this Bitmap bm, string message)
{
// Make sure the image is big enough.
byte[] messageBytes = Encoding.Unicode.GetBytes(message);
int numMessageBytes = messageBytes.Length;
byte[] lengthBytes = BitConverter.GetBytes(numMessageBytes);
int numLengthBytes = lengthBytes.Length;
int numMessageBits = 8 * (numMessageBytes + numLengthBytes);
int numAvailableBits = 3 * bm.Width * bm.Height;
if (numMessageBits > numAvailableBits)
throw new IndexOutOfRangeException(
"The message is too big to fit in the image. " +
$"The message is {numMessageBits} bits long but " +
$"the image can hold only {numAvailableBits} bits.");

// Hide the message length.
Bitmap bmCopy = new Bitmap(bm);
int x = 0, y = 0;
RGB component = RGB.R;
StegBytes(bmCopy, lengthBytes, ref x, ref y, ref component);

// Hide the message bytes.
StegBytes(bmCopy, messageBytes, ref x, ref y, ref component);

// Return the bitmap.
return bmCopy;
}

When we later want to decode the data, we need to know how long the message is so we know how many bits to pull out of the image. This method solves that problem by first storing the length of the message in the data.

The method begins by converting the message string into an array of bytes. The code uses Unicode encoding, so it can store Unicode messages. After converting the string into an array, the code gets the array's length.

Next, the code uses the BitConverter.GetBytes method to convert the message's length into an array of bytes. It also gets that arrays' length. The code then calculates the number of bits needed to store the message together with its length, and the number of bits that are available in the image. If there isn't enough storage space, the method throws an exception.

The rest of the method is fairly straightforward. It makes a copy of the bitmap and then uses the StegBytes method to store the length of the message in the image. It then uses StegBytes again to store the message's bytes and returns the new bitmap.

The example solution's decoding methods undo the steps performed by the encoding methods in reverse order. They're reasonably straightforward if you understand how the encoding methods work, so I won't show them here. You may want to try to write them yourself before you download the example solution.

The example solution includes code to load and save image files. It also includes some code to prevent you from closing the program without saving a newly encoded image. (Because I kept doing that accidentally during testing.)

This example stores a Unicode message, but the StegBytes method just stores an arbitrary array of bytes so it can store just about anything. For example, you could encrypt a message and then store the encrypted version in an image. Then someone else cannot read your message, even if they suspect it is there. Just remember that all data is destroyed if you save the image in a lossy format such as JPG of GIF.

Download the Steganography example solution to see additional details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.106.135