Monday, October 18, 2010

Steganography

The word steganography means "concealed writing" from the Greek words steganos meaning "covered", and graphein meaning "to write".

This is very ancient technology. So I’ll try to explain how it works.
It’s not a secret that every color can be represented as tuples of numbers, typically as three or four values or color components (e.g. RGB and CMYK).

It’s possible to use least significant bits of each color component for data storage. Thus it’s possible to use images for information hiding or steganography.
Let’s estimate the color component loss level for data storage.
The maximum loss level for 1 bit for color component coding will be 1b = 1 and the error will be:
1/255 * 100 % = 0,392 %
The maximum loss level for 2 bits per color component coding will be 11b = 3. So the maximum error per color component is equal:
3/255 * 100 % = 1,176 %
For 3 bits the maximum loss level will be 111b = 8 and the error will be:
8/255 * 100 % = 3,137 %
In common, human eye couldn’t distinguish so little difference in colors.

The picture below shows a principle of steganography using 2 bits per color channel coding. 
 
To hide ‘A’ char using 2 bits per color component coding required 4 color components or 2 pixels (RGB and yet another R).
Below you can see source code in C# for 2 bits per color component images coding and decoding.

[Show / hide source code]
//---------------------------------------------------------------------
// CodeStegoImage
//---------------------------------------------------------------------
private Bitmap CodeStegoImage(Image inputImage, string inputText)
{     
  string header = "DSTG"; // Steganorgaphy header
  int textLen = header.Length + inputText.Length + 4; // 4 bytes (65535 chars) for text length
  string lenStr = textLen.ToString("0000"); // make formated NNNN text length string
  string text = header + lenStr + inputText;

  byte[] bytes = new byte[text.Length * 4 + 4]; // 4 bytes for char using 2 bits coding + 4 bytes extra

  for (int i = 0; i < text.Length; i++)
  {
    bytes[i*4+0] = (byte)((System.Convert.ToChar(text[i]) & (byte)System.Convert.ToInt32("11000000", 2)) >> 6);
    bytes[i*4+1] = (byte)((System.Convert.ToChar(text[i]) & (byte)System.Convert.ToInt32("00110000", 2)) >> 4);
    bytes[i*4+2] = (byte)((System.Convert.ToChar(text[i]) & (byte)System.Convert.ToInt32("00001100", 2)) >> 2);
    bytes[i*4+3] = (byte)(System.Convert.ToChar(text[i]) & (byte)System.Convert.ToInt32("00000011", 2));
  }

  Bitmap bmIn = new Bitmap(inputImage);   // input bitmap
  Bitmap bmOut = new Bitmap(inputImage);  // output bitmap
  int counter = 0;

  for (int i = 0; i < inputImage.Height; i++)
  {
    for (int j = 0; j < inputImage.Width; j++)
    {
      Color colIn = bmIn.GetPixel(j, i);

      // clear 2 LSB
      uint ro = (uint)colIn.R & 0xFC; // 0xFC = 11111100b
      uint go = (uint)colIn.G & 0xFC;
      uint bo = (uint)colIn.B & 0xFC;

      Color colOut; // output color
      if (counter < text.Length * 4) // 4 bytes per char
      {
        colOut = Color.FromArgb((int)ro + bytes[counter + 0], (int)go + bytes[counter + 1], (int)bo + bytes[counter + 2]);
        counter += 3; // +3 bytes to next RGB pixel
      }
      else
        colOut = colIn;
      bmOut.SetPixel(j, i, colOut);
    }
  }
  return bmOut;
}

* This source code was highlighted with Source Code Highlighter.