How To Hide a Secret Message in an Image File – Steganography in Python

python cryptography steganography

When you see an image file, do you just see, – the visual image it represents? Or is it possible that there’s much more to it? Maybe there’s some secret hidden in the image that you don’t know?

Actually, yes. It is already an established cryptography practice to hide information in something like image files. This is called – Steganography.


What Is Steganography?

Steganography is a way of hiding critical information. Unlike cryptography, which focuses on encrypting data, steganography focuses on concealing the data, and thus the intended secret message does not attract attention to itself as an object of scrutiny.

Today, we will create a simple Python program that hides a secret message in an image file without changing what the image looks like.

The source code for this Steganography tutorial can be found at chen-yumin/steganography-python.

The idea behind this image-based steganography is simple. Image files are composed of digital data that describes what is in the picture, usually the colors of all the pixels. Take a 24-bit color bitmap image for example, each pixel is comprised of 3 channels of 8-bit color. However, the least significant bit (LSB) in these 8 bits carries very little weight, and therefore, even if it is somehow lost, you will barely notice any difference in the visual of the image.

Conceal the Message

In this example, we will conceal a hidden message in a 8-bit grayscale image, replacing each pixel’s LSB to store our message.

def to_bit_generator(msg):
    for c in (msg):
        o = ord(c)
        for i in range(8):
            yield (o & (1 << i)) >> i

First of all, the secret message gets converted to a Python generator which returns 1 bit of the message each time. For simplicity we will just read the file and use it as our secret hidden message. If you’d like to hide something else, feel free to change this to another file.

# Create a generator for the hidden message
hidden_message = to_bit_generator(open("", "r").read() * 10)
# Read the original image
img = cv2.imread('original.png', cv2.IMREAD_GRAYSCALE)
for h in range(len(img)):
    for w in range(len(img[0])):
        # Write the hidden message into the least significant bit
        img[h][w] = (img[h][w] & ~1) | next(hidden_message)
# Write out the image with hidden message
cv2.imwrite("output.png", img)

To keep things simple, the image file is read as IMREAD_GRAYSCALE grayscale. This reduces the complexity of the example so we don’t have to worry about the color dimension.

We loop through every pixel of this image, using the or operation to override its least significant bit. And here we go, we have our secret message hidden in this image.

Note that this file is saved as .png, which is a lossless compression format. You can also save it as some uncompressed image file formats, such as .bmp. However, if it is saved as .jpg, the secret hidden message will be lost due to the lossy compression algorithm.

Run the script, and here we have an image file with hidden data:

original.png output.png
Original Image Processed Image with Hidden Data

Compare the above two image files, can you notice any visual difference? The image on the left is the original one; the right is the processed image with hidden data. They all look exactly the same, but if you read it in a hex editor you will be able to see the actual content has been completely changed.

Restore the Message

Now that we have generated the image file with hidden data, let’s read back from the file and see if we can restore the hidden message.

# Read the image and try to restore the message
img = cv2.imread('output.png', cv2.IMREAD_GRAYSCALE)
i = 0
bits = ''
chars = []
for row in img:
    for pixel in row:
        bits = str(pixel & 0x01) + bits
        i += 1
        if(i == 8):
            chars.append(chr(int(bits, 2)))
            i = 0
            bits = ''

Run the script above to restore the hidden message. Can you see it?

Restored Hidden Message

Yes, the above textual information was all read from the output.png image file. Can you believe that? The next time when you see an image file, maybe you won’t take it as it is any more.

Chen Yumin
Chen Yumin

Hi, my name is Chen Yumin.
I am the author of the stuff you're reading right now.

Latest Project

Connect With Me

Enjoy what you see? Follow me on:


Subscribe via RSS