How to Use Steganography to Hide Secret Data in Images in Python

Abdou Rockikz · 31 oct 2019

Abdou Rockikz · 7 min read · Updated nov 2019 · Ethical Hacking

What is Steganography

Steganography is the practice of hiding a file, message, image or video within another file, message, image or video. The word steganography is derived from the Greek words steganos (meaning hidden or covered) and graphe (meaning writing).

It is often used among hackers to hide secret messages or data within media files such as images, videos or audio files. Even though there are many legitimate uses for Steganography such as watermarking, malware programmers have also been found to use it to obscure the transmission of malicious code.

In this tutorial, we gonna write a Python code to hide text messages using a technique called Least Significant Bit.

What is Least Significant Bit

Least Significant Bit (LSB) is a technique in which last bit of each pixel is modified and replaced with the data bit. This method only works on Lossless-compression images, which means that the files are stored in a compressed format, but that this compression does not result in the data being lost or modified, PNG, TIFF, and BMP as an example, are lossless-compression image file formats.

As you may already know, an image consists of several pixels, each pixel contains three values (which are Red, Green, Blue), these values range from 0 to 255, in other words, they are 8-bit values. For example, a value of 225 is 11100001 in binary and so on.

Let's take an example of how this technique works, say I want to hide the message "hi" into a 4x4 image, here are the example image pixel values:

[(225, 12, 99), (155, 2, 50), (99, 51, 15), (15, 55, 22),
(155, 61, 87), (63, 30, 17), (1, 55, 19), (99, 81, 66),
(219, 77, 91), (69, 39, 50), (18, 200, 33), (25, 54, 190)]

By looking at the ASCII Table, we can convert this message into decimal values and then into binary:

0110100 0110101

Now, we iterate over the pixel values one by one, after converting them to binary, we replace each least significant bit with that message bits sequentially (e.g 225 is 11100001, we replace the last bit, the bit in the right (1) with the first data bit (0) and so on).

This will only modify the pixel values by +1 or -1 which is not noticable at all, you can use 2-Least Significant Bits too which will modify the pixels by a range of -3 to +3.

Here is the resulting pixel values (you can check them on your own):

[(224, 13, 99),(154, 3, 50),(98, 50, 15),(15, 54, 23),
(154, 61, 87),(63, 30, 17),(1, 55, 19),(99, 81, 66),
(219, 77, 91),(69, 39, 50),(18, 200, 33),(25, 54, 190)]

Python Implementation

Now that we understand the technique we gonna use, let's dive in to the Python implementation, we gonna use OpenCV to manipulate the image, you can use any other imaging library you want (such as PIL):

pip3 install opencv-python numpy

Open up a new Python file and follow along:

import cv2
import numpy as np

Let's start off by implementing a function to convert any type of data into binary, we will use this to convert the secret data and pixel values to binary in the encoding and decoding phase:

def to_bin(data):
    """Convert `data` to binary format as string"""
    if isinstance(data, str):
        return ''.join([ format(ord(i), "08b") for i in data ])
    elif isinstance(data, bytes) or isinstance(data, np.ndarray):
        return [ format(i, "08b") for i in data ]
    elif isinstance(data, int) or isinstance(data, np.uint8):
        return format(data, "08b")
    else:
        raise TypeError("Type not supported.")

The below function will be responsible for encoding secret_data into the image:

def encode(image_name, secret_data):
    # read the image
    image = cv2.imread(image_name)
    # maximum bytes to encode
    n_bytes = image.shape[0] * image.shape[1] * 3 // 8
    print("[*] Maximum bytes to encode:", n_bytes)
    if len(secret_data) > n_bytes:
        raise ValueError("[!] Insufficient bytes, need bigger image or less data.")
    print("[*] Encoding data...")
    # add stopping criteria
    secret_data += "====="
    data_index = 0
    # convert data to binary
    binary_secret_data = to_bin(secret_data)
    # size of data to hide
    data_len = len(binary_secret_data)
    for row in image:
        for pixel in row:
            # convert RGB values to binary format
            r, g, b = to_bin(pixel)
            # modify the least significant bit only if there is still data to store
            if data_index < data_len:
                # least significant red pixel bit
                pixel[0] = int(r[:-1] + binary_secret_data[data_index], 2)
                data_index += 1
            if data_index < data_len:
                # least significant green pixel bit
                pixel[1] = int(g[:-1] + binary_secret_data[data_index], 2)
                data_index += 1
            if data_index < data_len:
                # least significant blue pixel bit
                pixel[2] = int(b[:-1] + binary_secret_data[data_index], 2)
                data_index += 1
            # if data is encoded, just break out of the loop
            if data_index >= data_len:
                break
    return image

Here is what we did:

  • Reading the image using cv2.imread() function.
  • Counting the maximum bytes available to encode the data.
  • Checking whether we can encode all the data into the image.
  • Adding a stopping criteria, this will be as indicator for the decoder to stop decoding whenever it sees this (feel free to implement a better and more efficient one).
  • Finally, modifying the last bit of each pixel and replacing it by the data bit.

Now here is the decoder function:

def decode(image_name):
    print("[+] Decoding...")
    # read the image
    image = cv2.imread(image_name)
    binary_data = ""
    for row in image:
        for pixel in row:
            r, g, b = to_bin(pixel)
            binary_data += r[-1]
            binary_data += g[-1]
            binary_data += b[-1]
    # split by 8-bits
    all_bytes = [ binary_data[i: i+8] for i in range(0, len(binary_data), 8) ]
    # convert from bits to characters
    decoded_data = ""
    for byte in all_bytes:
        decoded_data += chr(int(byte, 2))
        if decoded_data[-5:] == "=====":
            break
    return decoded_data[:-5]

We read the image and then get all the last bits of every pixel of the image. After that, we keep decoding until we see that stopping criteria.

Let's use these functions:

if __name__ == "__main__":
    input_image = "image.PNG"
    output_image = "encoded_image.PNG"
    secret_data = "This is a top secret message."
    # encode the data into the image
    encoded_image = encode(image_name=input_image, secret_data=secret_data)
    # save the output image (encoded image)
    cv2.imwrite(output_image, encoded_image)
    # decode the secret data from the image
    decoded_data = decode(output_image)
    print("[+] Decoded data:", decoded_data)

I have an example PNG image here "image.PNG", use whatever image you really want, just make sure it is a Lossless-compression image format as discussed earlier.

After the execution of the script, it will write another file "encoded_image.PNG" which has exactly the same image looking, but with secret data encoded in it, here is the output:

[*] Maximum bytes to encode: 125028
[*] Encoding data...
[+] Decoding...
[+] Decoded data: This is a top secret message.

Awesome ! You just learned how you can implement Steganoghraphy in Python on your own !

As you may notice, the resulting image will look exactly the same as the original image, this is because we're only modifying the pixel values by 1. So whenever a person sees this image, he/she won't be able to detect whether there is a hidden data within it.

Finally, here are some ideas and challenges you can do:

  • Encrypting the data before encoding it in the image (this is often used in Steganography).
  • Encode an entire file of any type in an image.
  • Use 2-Least Significant Bits technique to encode more data.
  • Encode massive amount of data in videos instead of images (you can do this with OpenCV as videos are just sequence of images).

THIS MAY INTERESTS YOUHow to Create a Reverse Shell in Python.

Happy Coding ♥

View Full Code
Sharing is caring!


Read Also





Comment panel

   
Comment system is still in Beta, if you find any bug, please consider contacting us here.