Steganograhy: Part 1
Tags: python, steganography, tutorial, programming
Published: 2023-08-06 09:11:09

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:

```python
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")
```

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.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">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.</div>
</div>

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.

```python
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'
```

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](https://gitlab.com/Vapourisation/steganography_python_example)

Header image courtesy of <a href="https://unsplash.com/@mgmaasen?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Michael Maasen</a> on <a href="https://unsplash.com/photos/AkYGy_ymFqo?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>