Skip to content
python steganography
Home » How to Hide Messages in Pictures with Python: Steganography

How to Hide Messages in Pictures with Python: Steganography

    Introduction

    Before implementing steganography in python, we need to understand what exactly is.

    Steganography is the practice of hiding information in plain sight.
    The word steganography comes from the Greek words steganos, meaning “covered” or “secret,” and graphein, meaning “writing.”

    Digital steganography can involve hiding information in digital images, audio files, and even video files.
    The advantage of digital steganography over traditional methods is that the message can be hidden in a file that is already in plain sight.
    For example, someone can hide a message in an image that is posted on a website. The recipient can see the message only if knows how to look for it.

    In order to have an additional level of security, the sender can encrypt the message before using some steganography technique.

    Steganography can be used for a variety of purposes, including sending secret messages, sharing sensitive information, and watermarking digital files.

    Overview of Steganography technique

    Looking at the hex of an image value, we can see that png and jpg files end always with the same byte sequence.
    In our case, we are on a Linux machine, so we’ll check with xxd,  but any hex editor would be ok:

    xxd -i image.jpg

    The output will be something similar to this

      .....
      0xef, 0x47, 0xf7, 0x94, 0x70, 0x51, 0xec, 0xa2, 0x9f, 0x4a, 0x7d, 0xe6,
      0xa8, 0x73, 0xff, 0x00, 0xc7, 0x3f, 0xff, 0xd9
    

    Doing that with many images, we can see that the last three bytes are a kind of terminator for a jpg. So the viewer will read no further.

    The same process can be done with png and we’ll see an output similar to this

      ...
      0xfc, 0xab, 0x38, 0x9d, 0x9d, 0xa2, 0xee, 0x3d, 0xc0, 0x77, 0xfe, 0xd1,
      0xef, 0xbf, 0xe8, 0x1f, 0x77, 0xc3, 0x86, 0x0d, 0x1b, 0x36, 0x6c, 0xb8,
      0x3c, 0xfc, 0x6f, 0x44, 0x3a, 0x0e, 0x7e, 0xe2, 0x52, 0x24, 0x49, 0x00,
      0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
    

    In this case, the ending sequence is composed of the last 12 bytes.

    What we are going to do, it’s just append our secret text to the image.

    Implementation of Steganography in Python

    Imports and global

    In order to write the script for steganography in python, we just need Python 3 without installing external libraries.

    The next step is to create a global dictionary containing the ending sequences for the two extensions we want to manage.

    import sys
    import os
    
    file_end = {
        ".png": b'\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82',
        ".jpg": b'\x3f\xff\xd9'
    }

    Methods

    The idea is just to write three methods:

    • append_secret
    • retrieve_secret
    • clear_secret
    def append_secret(filename, file_extension, secret):
        with open(f"{filename}{file_extension}", "ab") as f:
            f.write(bytes(secret, encoding="utf-8"))

    This code opens a file in “append binary” mode and writes a secret to it.

    def retrieve_secret(filename, file_extension):
        if not file_extension in file_end:
            print("Format not supported!")
        else:
            with open(f"{filename}{file_extension}", 'rb') as f:
                buff = bytes(f.read())
                index = buff.index(file_end[file_extension])
    
                return buff[index+len(file_end[file_extension]):].decode('utf-8')

    The program will check if the file extension is in the dictionary “file_end”.
    If it is, the program will open the file and read it as a byte array.
    It will then look for the index of the file ending in the byte array.
    Once it finds the index, it will decode the byte array from that point onwards (the secret) and return it out as a string.

    def clear_secret(filename, file_extension):
    
        if not file_extension in file_end:
            print("Format not supported!")
        else:
            buff = ''
            with open(f"{filename}{file_extension}", 'rb+') as f:
                buff = bytes(f.read())
                index = buff.index(file_end[file_extension])
    
                f.truncate(index+len(file_end[file_extension]))

    The code will read in the file, find the index of the image terminator, and then truncate the file at that index.

    At this point we just need to use them into the main.

    The main

    In the main, we want to use the script in 3 ways:

    • Append: will append the secret to the image
    • Retrieve: will retrieve the secret from the file
    • Clear: will clear the secret
    if __name__=="__main__":
        request = sys.argv[1]
        filename, file_extension = os.path.splitext(sys.argv[2])
    
        if request == "r":
            secret = retrieve_secret(filename, file_extension)
            print(secret)
        
        elif request == "a":
            append_secret(filename, file_extension, sys.argv[3])
        
        elif request == "c":
            clear_secret(filename, file_extension)
        
        else:
            print("[!] Wrong request, please use 'r' or 'a'")

    Put all together

    import sys
    import os
    
    file_end = {
        ".png": b'\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82',
        ".jpg": b'\x3f\xff\xd9'
    }
    
    def retrieve_secret(filename, file_extension):
        if not file_extension in file_end:
            print("Format not supported!")
        else:
            with open(f"{filename}{file_extension}", 'rb') as f:
                buff = bytes(f.read())
                index = buff.index(file_end[file_extension])
    
                return buff[index+len(file_end[file_extension]):].decode('utf-8')
    
    def append_secret(filename, file_extension, secret):
        with open(f"{filename}{file_extension}", "ab") as f:
            f.write(bytes(secret, encoding="utf-8"))
    
    def clear_secret(filename, file_extension):
    
        if not file_extension in file_end:
            print("Format not supported!")
        else:
            buff = ''
            with open(f"{filename}{file_extension}", 'rb+') as f:
                buff = bytes(f.read())
                index = buff.index(file_end[file_extension])
    
                f.truncate(index+len(file_end[file_extension]))
    
    if __name__=="__main__":
        request = sys.argv[1]
        filename, file_extension = os.path.splitext(sys.argv[2])
    
        if request == "r":
            secret = retrieve_secret(filename, file_extension)
            print(secret)
        
        elif request == "a":
            append_secret(filename, file_extension, sys.argv[3])
        
        elif request == "c":
            clear_secret(filename, file_extension)
        
        else:
            print("[!] Wrong request, please use 'r' or 'a'")

    Usage

    Append:

    python3 main.py a image.jpg "Secret phrase"

    Retrieve:

    python3 main.py r image.jpg
    

    Clear:

    python3 main.py c image.jpg
    

    Conclusion and improvements

    This technique is perfect for keeping secrets safe, and it’s a lot of fun to do. Give it a try and see for yourself!

    Surely steganography in python is not the state of art for security, but hardly someone will check the hex values of a picture.

    If you want another level of security, try an XOR encoding on the message, maybe you will like this article.