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.
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:
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 visually noticable at all, you can also 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)]
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 * image.shape * 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 = int(r[:-1] + binary_secret_data[data_index], 2) data_index += 1 if data_index < data_len: # least significant green pixel bit pixel = int(g[:-1] + binary_secret_data[data_index], 2) data_index += 1 if data_index < data_len: # least significant blue pixel bit pixel = 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 the
encode() function does:
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)
The above code will take
image.PNG image and encode
secret_data string into it and saves it into
encoded_image.PNG. After that, we use
decode() function that loads the new image and decodes the hidden message in it.
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.
So we can decode about 122KB (125028 bytes) on this particular image, this will vary from image to another, based on its resolution size.
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.
Also, if you're familiar with Linux commands, you can also perform Steganography using standard linux commands.
Finally, here are some ideas and challenges you can do:
Learn also: How to Extract Image Metadata in Python.
Happy Coding ♥View Full Code