I'm implementing image steganography, using the LSB of each pixel. If size of secret text is low (<10) it works fine, but it fails for longer texts.
My code:
private void processButtonLSB_Click(object sender, EventArgs e)
{
String dest = destLSBText.Text;
Bitmap img = (Bitmap)Bitmap.FromFile(dest); // bitmap of the given image
byte[] temp = GetBytes(this.secretText.Text);
byte[] secretText = new byte[temp.Length + 1];
temp.CopyTo(secretText, 0);
secretText[temp.Length] = 0x0; // adding last 0 byte
BitArray bits = new BitArray(secretText); // parsing bytes to bits of secret text
StringBuilder sb = new StringBuilder(); // debug feature
for (int i = 0; i < bits.Length; i++)
{
bool bit = bits.Get(i); // getting i bit of secret text
int x = i / img.Width;
int y = i % img.Height;
int argb = img.GetPixel(x, y).ToArgb(); // getting argb of the original image
bool argbBit = argb % 2 != 0; // getting last bit of the pixel
if (bit && !argbBit || !bit && argbBit) // ensure if we need to make changes
{
setBit(ref argb, 0, !argbBit); // changing last bit
img.SetPixel(x, y, Color.FromArgb(argb)); // changing bitmap
}
if (sb.Length > 0)
sb.Append(",");
sb.Append("0x" + argb.ToString("X4")); // adding modified pixel to debug #2
}
MessageBox.Show(sb.ToString()); // showing all pixels with payload
saveImage(img, ImageFormat.Png);
}
private void retrieveButtonLSB_Click(object sender, EventArgs e)
{
String dest = destLSBText.Text;
Bitmap img = (Bitmap) Bitmap.FromFile(dest); // bitmap with payload
List<bool> bits = new List<bool>();
byte[] foundBytes = null;
StringBuilder sb = new StringBuilder(); // debug feature
for (int i = 0; i < img.Width * img.Height; i++) // iterating thru all pixels until zero byte is met
{
int x = i / img.Width;
int y = i % img.Height;
int argb = img.GetPixel(x, y).ToArgb(); // getting argb
bool bit = argb % 2 != 0; // last bit
bits.Add(bit);
if (sb.Length > 0)
sb.Append(",");
sb.Append("0x" + argb.ToString("X4")); // adding argb to debug #3
if (bits.Count % 8 == 0) // checking if last byte is 0x0
{
foundBytes = new byte[bits.Count / 8];
BitArray bitArray = new BitArray(bits.ToArray());
bitArray.CopyTo(foundBytes, 0);
if (foundBytes[foundBytes.Length - 1] == 0x0)
break;
}
}
MessageBox.Show(sb.ToString()); // showing all pixels with payload
if (foundBytes != null)
secretText.Text = GetString(foundBytes);
}
processButtonLSB_Click
creates an image with the given hidden text, and retrieveButtonLSB_Click
retrieves hidden text from an image.
I added message boxes to detect the pixels on each step, and got the following results
Original image (first pixels)
0xFF0C6128,0xFF0C6128,0xFF0B6027,0xFF0B6027,0xFF0A5F26,0xFF0A5F26,0xFF0A5D25,0xFF0A5D25,...
Changed image (from #1)
0xFF0C6128,0xFF0C6128,0xFF0B6026,0xFF0B6027,0xFF0A5F26,0xFF0A5F27,0xFF0A5D25,0xFF0A5D24,...
Saved image (from #2)
0xFF0C6128,0xFF0C6129,0xFF0B6027,0xFF0B6027,0xFF0A5F27,0xFF0A5F26,0xFF0A5D24,0xFF0A5D24,...
But why? Maybe I have made a mistake? I checked everything twice and did not find anything.
The code that saves the image:
private String saveImage(Image img, ImageFormat format)
{
String path = getSavePath(format);
if(path != null)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo ici = null;
foreach (ImageCodecInfo codec in codecs)
{
if (codec.MimeType == "image/png")
ici = codec;
}
EncoderParameters ep = new EncoderParameters();
ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
img.Save(path, ici, ep);
return path;
}
return null;
}
Other dependencies (I'm sure it's correct)
private bool getBit(int b, int bitNumber)
{
return (b & (1 << bitNumber)) != 0;
}
private void setBit(ref int b, int bitNumber, bool value)
{
int mask = 1 << bitNumber;
if(value)
b |= mask;
else
b &= ~mask;
}
private byte[] GetBytes(string str)
{
return Encoding.UTF8.GetBytes(str);
}
private string GetString(byte[] bytes)
{
return Encoding.UTF8.GetString(bytes);
}