How to hide text in images with python.

Subscribe to my newsletter!

As Wikipedia states Steganography is the practice of concealing a message within another message or a physical object and today, we are going to do this with the programming language Python. We will make a program that writes text to images and reads it from there. We assign pixel values to each possible character. This is just a barebones version that you can use as a basis or inspiration for other programs like this.

So let us get started.

Python Imports

For this little program, we need three python modules/libraries. We get the Image class from PIL so we can manipulate images. Then we also get ArgumentParser from argparse, so we have an easy time asking the user for arguments. Last but not least, we get string which has some constants containing letters and digits.

# Imports
from PIL import Image
from argparse import ArgumentParser
import string

Setting up pythons ArgumentParser

Now that we have the ArgumentParser class, we must set it up. For this, we make a new ArgumentParser object, and then we call the add_arguement() three times. We add the positional argument path, which has to be always specified. Then also the mode which is read by default. And also the text argument, which we only have to supply when the mode is write. Finally, we also parse the arguments with parse_args() and save them to a variable.

# Command Line Arguments
parser = ArgumentParser()

# Keep in mind this will only work with uncompressed images (png)
parser.add_argument('path')
parser.add_argument('-mode', default="read")
parser.add_argument('-text')

arguments = parser.parse_args()

Opening the image

Now we use the Image class to open the image at the path the user has specified. We then get the width and height.

# Open the image.
image = Image.open(arguments.path)

# Get the image size and save it.
width, height = image.size

Setting up Variables

Next, we define some variables. We first have two variables that contain the RGB tuples of the starting and ending pixel. This is used to know where the message starts and ends, but we currently only use the endingPixel variable.

After that, we also define two dictionaries that will contain the relations between letters and pixels.

startingPixel = (10, 10, 255)
endingPixel = (255, 20, 20)

# Dictionary which hold the relations between pixels and characters
lettersToPixels = {}
pixelsToLetters = {}

To fill these dictionaries, we use a loop. We enumerate over the ascii_letters, digits, and ' ' and store them in the dictionaries.

# Making the relations using the string library
for i, letter in enumerate(string.ascii_letters + string.digits + ' '):
    lettersToPixels[letter] = i
    pixelsToLetters[i] = letter

Writing to an image

Now let us get to hiding messages in images. If the mode specified by the user is write, we know the user wants to write. But we also check if the text was set. Following that, we also assert if the text length is greater than the image width.

if arguments.mode == "write":
    if arguments.text != None:
        # check if the string would exceed the maximum ( width )
        assert len(arguments.text) < width

After that, we draw the starting pixel onto the image at the position (0, 0), which is the top left.

        # Draw the starting Pixel
        image.putpixel((0, 0), startingPixel)

After that, we loop / enumerate over the text the user has given us. We draw the pixel representing this character at (index+1, 0), meaning the top row of pixels will contain the message. We added one because we already drew a pixel at (0, 0). The color is made up of (11, lettersToPixels[letter], 11), so the middle color, green, will change depending on the letter.

        # Draw a pixel for each letter in the test
        for index, letter in enumerate(arguments.text):

            # The Middle value (g = green) is the number in the dictionary
            image.putpixel((index+1, 0), (11, lettersToPixels[letter], 11))

At the end of the writing part, we also draw the ending pixel after the letters and save the image.

        # Draw the ending Pixel
        image.putpixel((index+2, 0), endingPixel)

        # Save the image to the same place
        image.save(arguments.path)

Read Function

Now, if the mode is read, we will try to read text from the image. To do this, we loop over the image until we encounter the ending pixel. We add one to the index at the end of the loop.

elif arguments.mode == "read":
    text = ''

    index = 1
    while True:

We get the pixel color (RGB) at the position. If the pixel is the endingPixel, we know that we are finished with reading, so we break out of the loop.

        # Loop throug each pixel in the top row
        pixel = image.getpixel((index, 0))

        # if the pixel is the ending pixel, we break the loop
        if pixel == endingPixel:
            break

After that, we try to get the letter associated with the pixel and add the letter to the text variable.

        try:
            # Get the letter from the dictionary with the g (green) value.
            text += pixelsToLetters[pixel[1]]
        except:
            pass

        index += 1

After the loop, we print out the text, so the user knows what was written in the image.

    # Print out the text
    print(text)

Showcase of the python program

Now let us see a showcase of the steganography program made with python. We use the Mona Lisa, which is now in the public domain because Leonardo da Vinci died more than 70 years ago.

showcase of the python program-

Conclusion of the python program

Excellent! You have successfully created a Program that writes and reads text hidden in images using Python code! See how you can add more features to this program, such as better hidden pixels or something.

Leave a Reply