Keeping Secrets Secret: Steganography with .NET

Keeping Secrets Secret: Steganography with .NET

teganography, literally “hidden writing,” is nowadays most often associated with embedding data in some form of electronic media. Data is hidden by adding or altering insignificant bits of information of a file. For example, an algorithm designed to embed a text message might slightly alter information describing the RGB composition of a pixel for an image file.

Figure 1 illustrates a typical steganography (or stego) application scenario. The application receives the data to hide as input?text, audio, video, or image?and the file in which data will be hidden, called the cover file. The stego file is the result of the process. Although it contains the original cover file data as well as the hidden stenographic information, the stego file is virtually identical to the cover file.

Figure 1. Stego Application Scenario: The stego application hides different types of data within a cover file. The resulting stego also contains hidden information, although it is virtually identical to the cover file.

This article introduces the most common stenography algorithms and techniques. Then, it shows how to design and implement a .NET library to hide text messages in 24-bit bitmapped (.bmp) files. The sample code includes both a command-line and a GUI application that serve as proof of concept and let you experiment with the techniques discussed.

Algorithms and Techniques
There are three different techniques you can use to hide information in a cover file:

Injection (or insertion). Using this technique, you store the data you want to hide in sections of a file that are ignored by the processing application. By doing this you avoid modifying those file bits that are relevant to an end-user?leaving the cover file perfectly usable. For example, you can add additional harmless bytes in an executable or binary file. Because those bytes don’t affect the process, the end-user may not even realize that the file contains additional hidden information. However, using an insertion technique changes file size according to the amount of data hidden and therefore, if the file looks unusually large, it may arouse suspicion.

Substitution. Using this approach, you replace the least significant bits of information that determine the meaningful content of the original file with new data in a way that causes the least amount of distortion. The main advantage of that technique is that the cover file size does not change after the execution of the algorithm. On the other hand, the approach has at least two drawbacks. First, the resulting stego file may be adversely affected by quality degradation?and that may arouse suspicion. Second, substitution limits the amount of data that you can hide to the number of insignificant bits in the file.

Generation. Unlike injection and substitution, this technique doesn’t require an existing cover file?this technique generates a cover file for the sole purpose of hiding the message. The main flaw of the insertion and substitution techniques is that people can compare the stego file with any pre-existing copy of the cover file (which is supposed to be the same file) and discover differences between the two. You won’t have that problem when using a generation approach, because the result is an original file, and is therefore immune to comparison tests.

Among the substitution techniques, a very popular methodology is the LSB (Least Significant Bit) algorithm, which replaces the least significant bit in some bytes of the cover file to hide a sequence of bytes containing the hidden data. That’s usually an effective technique in cases where the LSB substitution doesn’t cause significant quality degradation, such as in 24-bit bitmaps.

For example, to hide the letter “a” (ASCII code 97, that is 01100001) inside eight bytes of a cover, you can set the LSB of each byte like this:

   10010010   01010011   10011011   11010010   10001010   00000010   01110010   00101011

The application decoding the cover reads the eight Least Significant Bits of those bytes to re-create the hidden byte?that is 0110001?the letter “a.” As you may realize, using this technique let you hide a byte every eight bytes of the cover. Note that there’s a fifty percent chance that the bit you’re replacing is the same as its replacement, in other words, half the time, the bit doesn’t change, which helps to minimize quality degradation.

The sample code uses the LSB algorithm; however, for further researches, you’ll find five additional approaches based on different techniques, such as Transform Domain, Spread Spectrum, Statistical method, Distortion, and Cover Generations in the Related Resources section (see the left column) of this article.

The Stego Library
With the basic theory out of the way, you can start to design and implement a basic .NET library that supplies a simple API for hiding a message in a 24-bit .bmp image. The class diagram in Figure 2 shows the interfaces and classes you are going to meet next in this article.

Figure 2. Stego Library Class Diagram: The stego library defines interfaces that support different types of cover and stego files. The BmpCoverFile and BmpStegoFile classes are concrete implementations that operate on 24-bit bitmapped images.

To handle both the cover and the stego file, you need two interfaces:

ICoverFileI is an abstraction of the cover and defines the CreateStegoFile method that creates the stego file for hiding a message. The method requires three parameters: the filename for the resulting (output) stego file, the message to hide, and a password that you’ll need later to retrieve the hidden message.

IStegoFile is an abstraction of the stego file, and defines the HiddenMessage property that extracts a hidden message.

The BmpCoverFile and BmpStegoFile classes implement the interfaces using the LSB substitution method. The LSBHelper class contains the implemented logic. Both classes use classes from the System.IO.Stream namespace to perform file I/O operations.

After implementing that API, it’s trivial to hide a message inside a .bmp file:

   ICoverFile cover = new BMPCoverFile("cover.bmp");   cover.CreateStegoFile("stego.bmp","Hello","MyPwd");   

Likewise, you can extract the message just as easily:

   IStegoFile stego = new       BMPStegoFile("stego.bmp","MyPwd");   Console.WriteLine(stego.HiddenMessage);

Because the library uses interfaces, you can extend it by implementing new classes that support different file types, such as other image file formats such as .jpg, .gif, .tiff etc., or audio/video file formats such as .wav, .mp3,?.avi, .mpeg, .asf, etc. For example, you might implement Mp3CoverFile and Mp3StegoFile classes that function similarly to hide a message inside an MP3 file.

The LSBHelper Class
The LSBHelper class is low-level code that operates on bits and bytes of cover and stego file. The Encode method hides an array of bytes inside data coming from a stream, and writes the result in another stream. Here’s the code:

   public static void Encode(Stream inStream,       byte[] message, Stream outStream)    {           int byteRead;      byte byteWrite;      int i = 0;      int j = 0;      byte bit;                            while((byteRead = inStream.ReadByte()) != -1)       {                                            byteWrite = (byte) byteRead;                        if (i

The Encode method uses the variable i to index the message byte-array and j to index a single bit of message[i]. The method reads a byte from the input stream, replaces the LSB with the bit at position j of message[i], and then writes the resulting byte to the output stream. Every 8 bits, the method processes the next element of the message array. After reading the entire input stream, the method checks if i is less than the message length. If true, there are still bytes to hide, but the cover is too small to contain them, so the method throws an exception.

The Bit class, used within the Encode method, contains the Replace and Extract methods that?by using bitwise operators?replace or extract a particular bit of a byte:

   public static void Replace(ref byte b, int pos,     byte value)    {           b = (byte) (value == 1 ? b | (1 << pos)         : b & ~(1 << pos));   }      public static byte Extract(byte b, int pos) {     return (byte) ((b & (1 << pos)) >> pos);   }

The LSBHelper.Decode method extracts hidden bytes from an input stream as shown below.

   public static byte[] Decode(Stream stream, int length)    {          byte[] hidden = new byte[length];     int i = 0;     int j = 0;     byte bit;     int byteRead;          while((byteRead = stream.ReadByte()) != -1)       {                                   // Extract the LSB of byteRead       bit = Bit.Extract((byte) byteRead, 0);              // Replace the bit at the j position with "bit"       Bit.Replace(ref hidden[i], j++, bit);              // Every 8 bits process another byte of "hidden"       if (j==8) {j=0; i++;}              // Check bytes left       if (i==length) break;                            }             // The hidden var contains the hidden bytes     return hidden;        }   

Decode is complementary to Encode?the method reads bytes from an input stream and composes the hidden message by extracting the LSB from every byte read. The length parameter represents the number of bytes to extract. After extracting length bytes, the method returns the hidden[] byte array.

The BMPCoverFile Class
The BMPCoverFile class is a concrete implementation of the ICoverFile interface that works with 24-bit .bmp files. Here's the interface code:

   public interface ICoverFile   {           IStegoFile CreateStegoFile(string stegoFileName,        string message, string pwd);         }

Note that the interface defines only a single CreateStegoFile method that creates a stego file (in the location passed in the stegoFileName parameter) hiding the message stored in a Unicode string. The pwd parameter, as you'll see, is a password used as additional protection?basically, you need that password every time you want to extract the hidden message from the stego file.

The BMPCoverFile class implements ICoverFile:

   public class BMPCoverFile : ICoverFile   {        private string fileName;                 public BMPCoverFile(string fileName) {       this.fileName = fileName;     }     ...     ...   }

The class constructor requires a fileName parameter that contains the filename of the cover file. The CreateStegoFile method creates the output stego file as shown in Listing 1.

The code in Listing 1 first checks whether the cover file is a 24-bit .bmp file. A .bmp file has a 54-byte header where the first byte is always "B," and the second "M." The bytes at positions 28-29 hold the number of bits used to represent the colors (the value must be 24, in this case). If either of those conditions evaluates to false the method throws an exception, because this class supports 24-bit .bmp images only. If both checks pass, the method writes the header to the stego file.

The next task is to retrieve the bytes of the Unicode string representing the message to hide. The code inserts the byte count (the length of the message) as the first four bytes of the byte-array via the AddLengthAhead method. That step's necessary because code to extract the message needs to know the message length.

The method encrypts the message bytes using the Encrypt method of CryptoHelper by passing the pwd parameter before hiding them in the stego file. The encryption provides additional protection?now, the message is both hidden and encrypted. You can find the implementation of CryptoHelper in the downloadable source code for this article.

Finally, the method hides the encrypted bytes into the cover by calling LSBHelper.Encode. When complete, it returns a BmpStegoFile instance that represents the created stego file.

The BMPStegoFile class
The BMPStegoFile class lets you extract a hidden message from a 24-bit .bmp stego file. It implements the IStegoFile interface that defines only a single HiddenMessage property:

   public interface IStegoFile   {        string HiddenMessage     {       get;     }      }

You use the HiddenMessage property to access the hidden message. The BMPStegoFile class implementation starts like this:

   public class BMPStegoFile : IStegoFile   {           private string fileName;      private string password;          private string hiddenMessage;               public BMPStegoFile(string fileName,          string password)       {         this.fileName = fileName;         this.password = password;      }           // Return the hidden message      public string HiddenMessage {         get          {            if (hiddenMessage==null)               ExtractHiddenMessage();                       return hiddenMessage;         }      }      ...      ...   }

The class has three private fields:

  • fileName? the stego file name
  • password?the password used to decrypt the message
  • hiddenMessage?the hidden message

The HiddenMessage property returns the value of the hiddenMessage field if it's not null; otherwise, it calls the ExtractHiddenMessage method to extract the hidden message from the stego file. Listing 2 shows the code for the ExtractHiddenMessage method.

After opening the file and seeking to the end of the header (byte 54), the ExtractHiddenMessage method shown in Listing 2 calls LSBHelper.Decode to get the first four bytes of the file, which, as you may remember, contain the length of the hidden message. Then, the method decrypts the length by calling CryptoHelper.Decrypt passing the four bytes and the password.

At this point, it again invokes LSBHelper.Decode to extract the full message. Then, something happens that may be a little obscure:

  • The method again inserts the length as the first four bytes of the byte array buffer using the ConcatByteArray method.
  • It decrypts the byte array with the CryptoHelper.Decrypt method.
  • It extracts only the message (without the first four bytes of the buffer) via the GetByteMessage method.

The reason that inserting the length again is essential for decrypting the message is because the BMPCoverFile.Encode method encrypts the length and the message together as a single block; therefore, due to the way the CryptoHelper class works, you must also decrypt the length and the message together to get the correct message.

Finally, the method sets the hiddenMessage property to the Unicode string obtained from the bytes.

Building Client Applications
In the sample source code for this article, you'll find two client applications for the stego library: a console and a GUI application. To use the console application, follow this syntax:

   stego.exe [-e    ] | [-d  ]

For example, you can create a stego file that uses a file named test.bmp as the cover file, and "goofy" as the password, and hide the message "Hello World!" as follows:

   > stego -e test.bmp "Hello World!" goofy test2.bmp

When you run the preceding command, the console application creates the stego file test2.bmp, which looks the same as test.bmp but contains the hidden message "Hello World!"

Figure 3. StegoGUI Application (Encoding): From the "Encoding" tab folder you can add a hidden message to a .bmp file.

To extract the message from test2.bmp use the -d option, as follows:

   > stego -d test2.bmp goofy

You pass in the name of the stego file and the password as command-line arguments. The result is:

   > Hello World!

Listing 3 shows the code for the console application.

The GUI application lets users encode and decode 24-bit .bmp files using a graphical interface. In Figure 3 you can see the "Encoding" tab folder of the application. Basically, the application lets you specify the file name of the cover, the resulting stego file, and a password, just like the console application.

Figure 4. StegoGUI Application (Decoding): From the "Decoding" tab folder you can extract the hidden message from a .bmp file.

You can extract a hidden message by clicking the "Decoding" tab folder (see Figure 4), and entering the name of the stego file and the password.

This article introduced the most popular algorithms and techniques about steganography and showed how to implement a .NET steganography library that you can easily expand and integrate in your projects. In addition, we implemented two applications (command-line, and GUI) that may be useful for hiding and extracting information to and from 24-bit .bmp files.


About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist