Second Title: Hiding Secret Messages in Cute Pictures of Dogs
I developed this activity to as an interesting way to introduce students taking the foundation level software class at Olin to image manipulation and binary math. Since I spent quite a bit of time working on it, I wanted to publish the entire thing as a blog post in case other people on the web are interested in steganography. This exercise was modified from a similar one found at Interactive Python, though this version encodes an image into another image instead of ASCII text.
Because I wrote this as a learning exercise, there’s a neat little repo that holds starter code and solution code. You can clone/fork it here. The starter code is in
This code uses the Python Pillow library. You can install it using pip if the package is missing from your computer by typing sudo pip install pillow into your terminal window.
If you don’t wanna learn about how to do steganography and just want a neat script that will hide messages in images for you, you can use the code in
What is steganography?
In a nutshell, the main goal of steganography is to hide information within data that doesn’t appear to be secret at a glance. For example, this sentence:
turns into
if you take the first letter of every word. Steganography is really handy to use, because people won’t even suspect that they’re looking at a secret message–making it less likely that they’ll want to try to crack your code. In the past, you may have tried to accomplish this kind of subterfuge using invisible inks or using special keywords with your friends. However, as fearless coders we have access to fancier ways to sneak data around. *evil laughter here*
The value of one pixel
There are multiple ways to hide things within other things, but today we will be working with images. A black and white image (not greyscale) is an easy thing to conceptualize, where a black pixel has a value of 1 and a white pixel as a value of 0.
Color images have three color channels (RGB), with pixel values of 0-255 for each pixel. So a pixel with the value (255,255,255) would be entirely white while (0,0,0) would be black. The upper range is 255 because it is the largest value that can be represented by an 8 bit binary number. Binary is a base-two paradigm, in contrast to decimal which is in base-ten, which means you calculate the value of a binary number by summing the 2s exponent of each place where a 1 appears.
So if we wanted to convert the number
You can also test this out in your Python interpreter. Binary numbers are automatically converted to integers so you don’t actually need to have a print statement. (It’s just there for clarity.)
>>> print(0b10001011)
139
>>> type(0b10001011)
<class ‘int’>
>>> 0b00001011
11
>>> 0b10001010
138
From our quick tests above, you can see that the leftmost bit place matters a lot more than rightmost bit because the rightmost bit only modifies the value of the number by 1. We saw that:
Because of this, we describe the leftmost bit as the “most significant bit” (MSB) while the rightmost bit is the “least significant bit” (LSB).
We can observe that its entirely possible to hide a black and white image inside an RGB image by changing the LSB of a pixel in a single color channel to correspond to the value of the image we want to hide.
Additionally, since changing the LSB doesn’t drastically change the overall value of the of 8 bit number, we can hide our data without modifying a source image in any detectable sort of way. You can test this out with any RGB color wheel to get a sense of how little difference there is between a color like (150, 50, 50) and (151, 50, 50)
Aside
The concept of MSB and LSB occurs in other contexts as well. For example, parity bits are used as a basic form of error checking. Additionally, because the LSBs will change rapidly even if the value of the bit changes a little, they are very useful for use in hash functions and checksums for validation purposes.
Decoding the sample image
Here we have a picture of a cute dog. However, this dog is hiding a very secret message… We’re going to decode it! This image is also included in the git repo under images/encoded_sample.png.

Image Source: DogBreedInfo.com
Provided in the starter code is a function called
The Python bin function is useful in converting between integer and binary. Since bin will convert an integer to a binary string, we need to do processing on the result. Also, since the hidden information is on the red_channel only from the original RGB image, we will use the
Here’s the template code for you to fill out:
def decode_image(file_location="images/encoded_sample.png"): encoded_image = Image.open(file_location) red_channel = encoded_image.split()[0] x_size = encoded_image.size[0] y_size = encoded_image.size[1] decoded_image = Image.new("RGB", encoded_image.size) pixels = decoded_image.load() for i in range(x_size): for j in range(y_size): pass #TODO: Fill in decoding functionality decoded_image.save("images/decoded_image.png")
And if you want to see the solution:
def decode_image(file_location="images/encoded_sample.png"): encoded_image = Image.open(file_location) red_channel = encoded_image.split()[0] x_size = encoded_image.size[0] y_size = encoded_image.size[1] decoded_image = Image.new("RGB", encoded_image.size) pixels = decoded_image.load() for i in range(x_size): for j in range(y_size): if bin(red_channel.getpixel((i, j)))[-1] == '0': pixels[i, j] = (255, 255, 255) else: pixels[i, j] = (0,0,0) decoded_image.save("images/decoded_image.png")
Note that all our images will be in .png format. This is because JPEG format compresses the image data, which means that data encoded into the LSB may be lost.
When we run this function on the image from earlier, we see a shocking message!
Encoding a secret message
Now that we can decode secret messages, it’s only natural that we want to encode some too! Provided in the starter code are a pair of functions called
The provided code is pretty empty because it’s really similar to the
def encode_image(text_to_encode, template_image): pass #TODO: Fill out this function
But if you want the solution:
def encode_image(text_to_encode, template_image): template_image = Image.open(template_image) red_template = template_image.split()[0] green_template = template_image.split()[1] blue_template = template_image.split()[2] x_size = template_image.size[0] y_size = template_image.size[1] #text draw image_text = write_text(text_to_encode, template_image.size) bw_encode = image_text.convert('1') #encode text into image encoded_image = Image.new("RGB", (x_size, y_size)) pixels = encoded_image.load() for i in range(x_size): for j in range(y_size): red_template_pix = bin(red_template.getpixel((i,j))) old_pix = red_template.getpixel((i,j)) tencode_pix = bin(bw_encode.getpixel((i,j))) if tencode_pix[-1] == '1': red_template_pix = red_template_pix[:-1] + '1' else: red_template_pix = red_template_pix[:-1] + '0' pixels[i, j] = (int(red_template_pix, 2), green_template.getpixel((i,j)), blue_template.getpixel((i,j))) encoded_image.save("images/encoded_image.png")
Pulling it all together
As promised, here is the script in its entirety. Hope you have fun sending hidden messages around!
-Sophie
how can i do image steganography using grey scale image. i need a help
Sorry that I’m about 2 years late to answering this, but grey scale is essentially RGB but with only 1 channel. You can hide data in that channel identically to the example I have above.
Even when i am changing the encoded message,it keeps showing me the default message no matter how many times i change it.
My encoded message just won’t show up
PLZ send help
Hi Sagmac, I’ll need to see some of your code to figure out what’s happening. Maybe you’re not pointing the decoder at the correct image file?
is there any one who has a full source code of a Steganography i really want the source code
“gettse29@gmail.com”
Hey there,
The source code is linked at the top of the post:
https://github.com/srli/image_steganography
If the image gets resized or cropped will the encoded message still remain inside it?
It’ll depend on how the resizing and cropping is done. Because the hidden message is simply layered into the cropped, if there isn’t too much distortion the message will still remain.
If the image gets cropped then the encoding will be destroyed. Then which steganography can be done to get out of this problem? And how to implement it?
it will be really great if you start a youtube series regarding this topic ! there are not many series dedicated to stegenography . thanks
Haha, maybe. I’ve always wanted to moonlight in cyberforensics 😉
Thank you for this post. Help me a lot 🙂
Glad I could help 🙂
Hey, thanks for sharing your code, I’ve had a great time doing the exercise. Do you know any interesting materials to expand in this area? It’s super-easy when comparing to C-like languages and I really enjoyed this one.
Hi Paul, I’m glad you liked it! Python is def the most enjoyable language for me to play around in because implementations are usually pretty straight forward. As for exercises, the students at Olin learn python via the Think Python book: http://greenteapress.com/wp/think-python-2e/. The later chapters have some more sophisticated problems to solve.
I’ve done a couple fun exercises using python with the Speech to Text and the Knight’s Tour problem, so you could try to implement those in a better way / with more features. Otherwise, feel free to check back since it is a goal of mine to do more tutorial/exploratory style posts.
Thank you for the response!
I’d love to read more of your articles. I’ll definitely check out this book, thanks for that too.
hi, how can i use this code to create an application in django?
I don’t know exactly how to implement such an application, but you should be able to integrate the python code within the django framework fairly easily as it doesn’t rely on too many external libraries.
Thanks for the precisely written blog on the subject. If possible please enlighten me on ‘How to retrieve the encoded message as text from the decoded image’.
Hi Nitin,
I’m not quite sure what you mean. The decode_solution function does extract the hidden message from the image with an encoded message though. Can you clarify what your confusion is?
I think he means extracting the decoded data as plain text, not as an image of text.
Ahh, okay. To do that, remember that ASCII is text rendered from 8 bit encoding. You can encode the text into the image the same way you’re encoding another image in a bunch of ways. For example, the letter ‘h’ is ‘01110011’ in binary and ‘i’ is ‘1110100’. Treat those numbers as the 1s and 0s you’re hiding in the LSB of the pixel and you can directly hide ASCII text. Decoding the message will be similar, but you’ll have to remember to look at the decoded values in groups of 8 to pull the ASCII text back out from the encoded image.