In very short words steganography is a method or technique to hide some message within another message or other kind of media (like image, painting, sound etc.). The masquerade is so subtle that person not expecting to find a hidden message won’t notice anything.
With the IT era possibilities are getting bigger and bigger. You can change just a one bit of each byte in i.e. image to store (encode) hidden images and the viewer won’t notice any difference in most cases. In the same, the size of encoded image is really impressive. You can store around 175 thousand letters (or seventeen thousand typical words) in a single 800×600 pixels image!
This article describes how to write a simple Delphi / Pascal code as an example of steganography — to encode (and decode) some mysterious text message within simple bitmap.
A typical 24-bit bitmap image (800×600 pixels) has 1 440 000 bytes (3 bytes per each pixel to store 24-bit color data). If you use just a single (i.e. last) bit of each of this image, you’ll “gain” that number (nearly one and a half million) of bits that you can change to encode your message. And the difference between original and resulting image won’t be noticeable by most of people.
Here you have an example:
Left image is original one while right image contains 849 letters / 14 lines of encoded message.
- hardware limitations — my computer is to slow to process bigger images effectively
- software limitations — webpages generally does not accept BMP images and I had to convert this one to JPG
the “whole picture” isn’t given here. But, believe me (or not) while quickly browsing through these two images (i.e. showing one after another over and over again) you won’t be able to see any difference. And, if you make a really deep and detailed examination, you’ll find just a slight difference of colours.
Slight change, because we’re changing (1) or not changing (0) just a last bit of each byte representing each color component (red, green or blue) of each pixel in the source image. We’re then using all these “ones” and “zeros” to encode actual message.
When (in our “encrypted” message) we hit with 1 (bit changed) then the resulting color gets darken (+1) on one, two or three of its color components. If we hit with 0 then there is no change at all.
And now, about the change. Can you (by just looking at an image, without examining it with a professional software) tell the difference between green 0, 0, 254 and green 0, 0, 255?
I’ll bet you can’t.
In its simplest form, every byte has the last bit used to hold the encoded information.
Let’s assume the following bytes structure:
Three consecutive pixels in the 24-bit bitmap. Each color component (red, green and blue) denoted by single byte, so three bytes per each pixel and nine bytes in total. I separated the last bit with a dot.
Now you “compose” eight such bits one byte of the encoded information (going from the top): 00100111 etc.
By modifying only the last bit (as above) you basically do not change the bitmap — this change won’t be noticed by nearly every eye. As mentioned in the introduction, in an average bitmap (800×600) you have 1,440,000 bytes, so you can store 180,000 encoded bytes, or about 175kB.
Delphi / Pascal code
This is ripped off from one of my bigger projects, so there might be some slight errors or problems, but the general idea should be clearly expressed.
To encode encodedMessage (single or multi-line string) into FileName (full path reference to bitmap file; may not exists) we can use something like that:
procedure TfrmMain.Encode(FileName, encodedMessage: String); const CYPHER_CODE = 'CYPHER~SIGNATURE'; var BMP: TBitmap; s: String; i, j, ch, nr: Integer; bByte: Byte; pbaLine: PByteArray; begin s := Copy(CYPHER_CODE + encodedMessage, 1, (cypherImage.Width * cypherImage.Height * 3) div 8); BMP := TBitmap.Create; BMP.Assign(cypherImage); try ch := 1; bByte := Ord(s[ch]); nr := 0; //For each line of the image... for i := 0 to BMP.Height - 1 do begin //Read the whole line pbaLine := BMP.ScanLine[i]; //Iterate through line, each position consist of a single bit for j := 0 to BMP.Width - 1 do begin //We're modifying the last bit only pbaLine[j] := ((pbaLine[j] shr 1) shl 1) or (bByte and 1); Inc(nr); //Is it the last bit? if nr = 8 then begin nr := 0; Inc(ch); if ch > Length(s) then bByte := 0 else bByte := Ord(s[ch]); end else bByte := bByte shr 1; end; end; try BMP.SaveToFile(FileName); except MessageBox(Handle, 'Error during saving of file!', PChar('Error: ' + IntToStr(GetLastError)), 16); end; finally BMP.Free; end; end;
To decode message out of bitmap file we can use something like that:
procedure TfrmMain.Decode(FileName: String): String; const CYPHER_CODE = 'CYPHER~SIGNATURE'; var BMP: TBitmap; s: String; i, j, k, nr: Integer; bByte, b: Byte; pbaLine: PByteArray; begin BMP := TBitmap.Create; try try BMP.LoadFromFile(FileName); imgImage.Picture.Bitmap.Assign(BMP); imgImage.Repaint; s := ''; nr := 0; bByte := 0; for i := 0 to BMP.Height - 1 do begin pbaLine := BMP.ScanLine[i]; for j := 0 to BMP.Width - 1 do begin //Reading each following bit bByte := bByte xor (pbaLine[j] and 1); Inc(nr); //Do we have the whole byte (8 bits)? if nr = 8 then begin //We need to reverse order of all bits b := 0; for k := 0 to 7 do begin b := b shl 1; b := b or (bByte and 1); bByte := bByte shr 1; end; //Adding new character to the decoded text. s := s + Chr(b); nr := 0; bByte := 0; end; bByte := bByte shl 1; end; end; //Is this truly a decoded message or some garbage? if Pos(CYPHER_CODE, s) = 1 then begin Delete(s, 1, Length(CYPHER_CODE)); end else s := ''; Result := s; except MessageBox(Handle, 'Error during reading of file!', PChar('Error: ' + IntToStr(GetLastError)), 16); end; finally BMP.Free; end; end;
We’re using a very simple signature (
CYPHER_CODE) that is added during encode process and read / verified during decode to make sure that the particular bitmap image truly includes “our” encoded image at the last bits, not some garbage.
Summary and remarks
Depending on what you want to encode and how you want to transfer file with encoded message, this article maybe useful for you in every-day work or will forever be your theoretical study only.
The biggest problem is that this is a very simple solution that resulting file to not use any compression or other kind of direct file modification algorithms. It will “die” completely on every file type that uses any of such.
Thus you can use it for example in:
- BMP image
- WAV audio file
The problem is that modern Internet uses JPG image files and MP3 audio files instead. If you want to use this technique for some harmful or security-related activities then delivering bitmaps or uncompressed audio file to someone will be quite very suspicious. Even when you compress them using i.e. Zip program. This is because people today generally are not using such files.
You can use APE format with lossless compression to get “smaller” version of a WAV file, but shipping such format will be suspicious as well, not mentioning that not everyone has a player able to play such files.
For a different reasons (corrupted or garbage resulting file) you cannot encode message inside text file (either Notepad’s or Word’s one etc.).
Some other remarks:
- Resulting image shouldn’t be modified by any external program. Encoded message should in theory “survive” simple graphical modifications like drawing on bitmap in Paint. But any more “intrusive” operation (like changing contrast) will destroy encoded message entirely or turn it into unreadable garbage in best case scenario.
- As mentioned above, you can try to encode message in a sound file (given that you have correct library to read and save such file formats in Delphi) then play it and you shouldn’t hear any difference despite encoded message.
- You can encode (using a way different technique, but still) a lot of information in NTFS file system’s streams. You can have hundreds of megabytes of data encoded this way and file managers will still claim that file has 10 kB size or so. But, that is a whole different story.
Don’t get me wrong. You can encode secret message into MP3, JPG or any other format that uses some strong compression algorithm. But dealing with such formats requires a far more research than this article can offer.