Steganography: how to hide python scripts in images
Introduction
Steganography is derived from the two greek words “Steganos” (covered) and “Graptos” (writing), it is the art of hiding information in plain sight dating back to the 4th BC when invisible ink was used to provide secure communication. Unlike cryptography, steganography does not draw attention to the secret message as it is often hidden in a trustful medium rather than being present but in an incomprehensible way.
In the digital era, steganography mediums include texts, audio, video and image files having specific steganographic techniques of each medium, also the uses of steganography has broadened to include watermarking (embedding a secret message for authenticity verification) and to enhance personal privacy (Hiding your own personal data. Imagine having all your work on a pen drive and losing it in collage).
In this article, we will go through the process of hiding a python script in an image, then retrieve it back (The scripts are available in my github). The encoder takes an image and a python script to be hidden, then returns the stego image (The same image but with the script hidden within). In order to extract the python script back, the stego image is passed to the decoder.
The above image is a comparison between the stego image (on the right) and the original image (on the left). Although the stego image contains a hidden python script within, no visual change could be perceived.
Understanding the basics
The Pixel is the smallest addressable element in an image responsible for representing a color at a specific location in the image. As seen in the image above, when zoomed into the hill, we can observe the color of each pixel, which as a whole, contribute to forming the edge of the hill and a lit sky. Each pixel derives its color from a combination of the primary additive colors (Red, Green and Blue) having 256 possible intensity for each color channel, making it possible to generate more than 16 million unique colors in total.
When comparing machines and humans in distinguishing shades of a color, it is a difficult task for humans, for example, the image on the left contains three close shades of the color red, a normal human will not be able to distinguish the difference. On the other hand, computers are able to accurately distinguish the difference as they deal with value of each color channel as seen in the “RGB” box below each image.
Spatial domain digital image steganography takes advantage of this limitation in humans by embedding tiny bits of the secret message in each pixel (slightly increase or decrease the RGB values) in a way which the difference of the resulting color after modification cannot be perceived by the human eye.
In order to achieve a better understanding of the embedding process, we need to know how the bit manipulation happens. The RGB value of each pixel is stored in binary (0s and 1s) and takes 24 bits which is equivalent to three bytes per pixel to store the value of each color channel (8-bit R, 8-bit G, 8-bit B). For example, the number “3” in the red channel will be represented as “00000011” in binary. Within a byte there are a total of eight bits as illustrated in the figure above, the rightmost bit has the least value (we get a value of 1 if the bit is on) and is known as the Least Significant Bit while the left most bit has the highest value (we get 128 if the bit is on) hence it is called Most Significant bit. LSB is chosen when selecting pixel RBG bits to be replaced by our secret message bits as it causes minimal changes when compared to MSB.
For Example, there are three color blocks in the image on the left, the leftmost block is the control, the middle block has the same bits combination as the control but the least significant bit of every color channel, notice how the change in values are insignificant and the difference in shade is impossible to detect. The rightmost block also has the same combination as the control but with the first MSB changed, notice how the impact is drastic on both value and color. In my script, i will be replacing two least significant bits with the secret file bits, this doubles the storage capacity and does not seem to have a noticeable impact on color .
The encoder script
Make sure you are using Python 3 to launch the script with both Python Image Library (PIL) and Numpy installed, latest versions are preferred. Below, is will go through the encoding function which is used to produce the stego image. However, detailed documentation for auxiliary functions will be present in the script which can be obtained by using the decode script on the image in the repository.
The first two functions “read_secret” and “import_image” are responsible for taking the python script and image file names including extension. and return the file and image as a 3D numpy array respectively. Then, the size of the script and the number of available pixels in the image (to be replaced by secret bits) is calculated. The “if” statement then determines if all the bits of our secret python script will be able to fit within the image, if not or user wanted to terminate the process, it will quit.
Then the dimension of the image is saved before it is flattened to a vector (1D) to ease the embedding process, it is important to save the dimension before flattening as we will reverse the vector back to the same dimension after embedding.
Next, the length of the python script is embedded into the first 4 pixels of the image. The “encode_capacity” function converts the length of the secret script into binary of 24 bits instead of 8 (this is done to accommodate large numbers as the length is often a large number), then replaces two LSB of each color channel of each of the four pixels with two bits from the 24-bit length.
After encoding the length payload of 24 bits to the image, the python file gets encoded 2 bits at a time to each color channel using the “encode_secret” function. Then the image dimension is restored and saved under the name “stego image.png”.
Above is an example of running the script, simply call the “encode” function which takes the name of the image you want to use and the name of the script you want to hide in it (all names must include extensions). After typing “y” and pressing enter, the stego image should appear in the directory of the encode script.
The decoder script
To decode the secret python script back from the image, 2 least significant bits are extracted from each RGB color channel, when the number of extracted bits reaches eight, a byte is formed and its converted to a character using the Unicode Transformation Format (UTF-8) and written to a file. As a security measure, the user has to provide the extension of the secret file, for example, “py” if the hidden file is a python script.
The first step when decoding is to read and flatten the stego image, the dimension information is ignored as our intention is only to extract the secret file.
Then, “decode_capacity” looks at the first four pixels (secret file size payload) and extracts two LSB from each value to determine the length of the secret script. This step is crucial to determine when to stop extracting LSB bits, especially when the secret file is not using all the available capacity of the image.
Finally, “decode_secret” starts taking 2 LSB bits from each color channel in each pixel after the size payload and reassembles them back to bytes. The bytes are converted to characters using the UTF-8 encoding before they are written to a file under the extension specified by user.
To decode the secret file, pass the stego image and the extension of the hidden file to the decode function, the script under the name “secret.py” will appear in the directory.
The end
In this article a spatial domain steganographic technique was used to hide the secret file, there are other techniques such as the transform domain technique, wavelet transform technique and spread spectrum technique. However, the spatial domain technique is know to have the highest payload capacity, hence, it is selected for this article.