DEV Community

Cover image for Steganograhy: Part 1
Thomas Pegler
Thomas Pegler

Posted on

Steganograhy: Part 1

Welcome to the first in a series of posts about Steganography. Something that I find both fascinating and oddly under-utilised. If you're reading this you likely have some idea of what it is, but just in case you don't, here's a brief description in the digital context.

Steganography is the practice of representing information within another message or physical object, in such a manner that the presence of the information is not evident to human inspection.

It's really that simple at a broad level. You take something you want to hide (target), another message/image (cover) and manipulate the cover material in such a way that the original is retrievable but not visible.

There are numerous applications for this such as monitoring assets as they pass through a system, tagging assets, sending encoded messages to people or even embedding metadata so that they aren't stripped out by automated processes.

There are multiple ways to achieve this but the main one and the one I'll focus on in this article is known as LSB (Least Significant Bit). This is where you take the pixel values of the cover image and alter the least significant bit to "store" data from your target image, one bit at a time. Here's a quick Python example to hide some text in an image:

from PIL import Image

def encode():
    start = '#####'
    stop = '*****'
    full = start + 'Some string that you want to encode into an image' + stop

    binary_text = ''.join('{0:08b}'.format(ord(x), 'b') for x in full)
    print(binary_text, len(binary_text))

    with Image.open("file.png") as im:
        i = 0
        w, h = im.size

        for x in range(0, w):
            for y in range(0, h):
                if i >= len(binary_text):
                    i = 0

                bit = binary_text[i]
                pixel = im.getpixel((x, y))

                if bit == "0":
                    # Is odd, should be even.
                    if pixel[0] % 2 != 0:
                        new_pix = (pixel[0] - 1, pixel[1], pixel[2])
                        im.putpixel((x, y), new_pix)
                else:
                    # Is even, should be odd.
                    if pixel[0] % 2 == 0:
                        new_pix = (pixel[0] - 1, pixel[1], pixel[2])
                        im.putpixel((x, y), new_pix)

                i += 1

        im.save("file_enc.png")
Enter fullscreen mode Exit fullscreen mode

With this, you translate some text into a string binary representation of the text, loop through every pixel and alter the Red value (which colour is mostly arbitrary, red just usually comes first when splitting out pixel data) to match the binary value. This will go through the entire image, loop through the binary text and encode it into the image. This might be overkill for you, but it gives the basic premise.

💡
Note the use of a start and stop string. This is important when encoding in a full loop since without it, you won't know where the actual string was encoded. This is also incredibly useful for robustness later on.

The decoding/retrieval part of this is simply the reverse. Get the pixel values, rebuild the binary string from the Red pixel value's LSB and then convert it back to a standard string.

def decode():
    start = '#####'
    stop = '*****'
    binary_stop = ''.join('{0:08b}'.format(ord(x), 'b') for x in stop)

    with Image.open("file_enc.png") as im:
        w, h = im.size
        binary_text = ''

        for x in range(0, w):
            for y in range(0, h):
                pixel = im.getpixel((x, y))

                if binary_text.endswith(binary_stop):
                    message = "".join([chr(int("".join(binary_text[i:i + 8]), 2)) for i in
                                       range(0, len(binary_text) - 8, 8)])

                    start_point = message.find(start) + len(start)
                    end = message.find(stop)
                    message = message[start_point:end]
                    return message

                if pixel[0] % 2 != 0:
                    binary_text += '1'
                else:
                    binary_text += '0'
Enter fullscreen mode Exit fullscreen mode

As I said, this is a very simple implementation though. It's done row by row, column by column in an image and repeats all over the image so is not very robust. It's also the most common form so not at all resistant to steganalysis.

That's all there is to it. Now, the above is not performant, it could be improved but it should provide a good base for starting to implement steganographic capabilities into your apps.

A full working example can be found on my Gitlab, here: https://gitlab.com/Vapourisation/steganography_python_example

Header image courtesy of Michael Maasen on Unsplash

Top comments (3)

Collapse
 
ranggakd profile image
Retiago Drago

Glad found someone with a good post about steganography. I just make one post with similar topic but focus on image instead. I would like to know your opinion on the way I approach it 😀

Collapse
 
vapourisation profile image
Thomas Pegler

A fellow steganography fan! It's so hard to find any good articles on it so I'm so happy other people are writing about it. It's so interesting and I really think it's going to be huge with the rise of AI.

Collapse
 
ranggakd profile image
Retiago Drago

I know right, it is such a fascinating field in the rise of AI and LLM as well