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 %
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 %
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 %
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.
// 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.