Author - Sivakumar RR
The word “Steganography” comes from Greek “steganographia” which combines the words “steganós”, meaning “covered or concealed” and “grahia” meaning “writing”. Even though most of the places this is considered as part of Cryptography where messages are concealed or encrypted during communication, Steganography conceals the fact that the message is communicated. In this technique, message content will be hidden inside another file which can be an audio, video, image or other format of files.
The main advantage of using Steganography over Cryptography alone is that the intended secret message does not attract attention to itself as an object of scrutiny. There are places where encryption is illegal where steganography can be an alternate solution. Although in some cases steganography can be used as an extra layer of security on top of encryption where the encrypted message can be communicated via a stego object. Various forms of steganography which are widely used now a days are
- Text Steganography
- Image Steganography
- Video Steganography
- Network Steganography
- Audio Steganography etc
Since all the above mentioned are self explanatory from their names itself, I’m not going into details of each and every one in this article.
As you can see in the drawing above, a stego object is generated from the process of embedding message (M) into cover object (C) with the stego key (K). In this article we will be going through the similar implementation for creating a steganographed image using Python programming language.Images are formed by a group of pixels representing RGB values. The primary colors Red, Green and Blue, based on the intensity of each of these colors in each pixel decides the color of the pixel. As represented in the image RGB values can be represented in binary format as well. We are going to achieve this goal by changing the RGB value in each pixel.
Picture Credit : Edureka Steganography tutorial |
In this tutorial, we will be using a default image we call it as “base_image.png” for all the encoding purposes and each time when a new encoding is processed there will be an output file created named “encoded_image.png”. We are mainly using PNG format here since JPG / JPEG image’s pixels will be compressed over transmission in most of the applications for performance and storage optimization, in such cases the data we are trying to embed will be lost or scrambled.
How LSB update process works here?
[(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)]
Take above RGB combination array as an image which we are using it as cover object and assume the message we wanted to encode inside this is “hi”. Using ASCII table we can convert the text to decimal and then to binary value where the “hi” will be represented as “0110100 0110101“. We will be iterating over the pixel values and changing the least significant bit (LSB) with the message value in binary. This update will be +1 or -1 operation so this will not make any noticeable impact on the image. Resulting image data will be as follows.
[(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 let’s try this in python, we will be using a few libraries for better and ease of implementation.
import cv2 #opencv-python for image processing
import types
import numpy as np
import subprocess
We will start with the user prompt for option selection to either choose with encode the data or decode the data.
a = input("\n 1. Encode the data (we will use a default image to encode data)\n 2. Decode the data \n Your input is: ")
userinput = int(a)
if (userinput == 1):
print("\nEncoding....")
encode_text()
elif (userinput == 2):
print("\nDecoding....")
print("Decoded message is ==> " + decode_text().decode())
print("")
else:
raise Exception("Enter correct input")
Let’s start with encoding, so one thing which we need to know here is, if we go ahead with only steganography then the data is actually hidden, not really secured since it's not encrypted and there are possibilities that others can read it by analyzing the pattern of the file.
We can add an extra layer for securing the data here, which is Encryption. As part of the encoding process let’s encrypt the data with a key and encode the encrypted message with the image. Later then the key can be shared with the user so the decoding user can use the key for decrypting the message.
from cryptography.fernet import Fernet
We will be using the cryptography library and Fernet for encryption.
def encode_text():
image = cv2.imread("base_image.png") # Read the input image using OpenCV-Python.
#details of the image
print("The shape of the image is: ",image.shape) #check the shape of image to calculate the number of bytes in it
data = input("Enter data to be encoded : ")
if (len(data) == 0):
raise ValueError('Data is empty')
key = Fernet.generate_key()
print("\n Secret Key ==> " + key.decode())
print("\n Keep the above mentioned key as secret, since the key is required to decrypt the data.")
enc_data = encrypt_message(data, key)
filename = "encoded_image.png"
encoded_image = hideData(image, enc_data) # call the hideData function to hide the secret message into the selected image
cv2.imwrite(filename, encoded_image)
print("\n Process completed, encoded image name 'encoded_image.png'")
Here we are using the Fernet to generate a random key for encryption and we are sharing the key with the user as part of the process. We can go further and implement the “hideData” method we use here.
def hideData(image, secret_message):
# calculate the maximum bytes to encode
n_bytes = image.shape[0] * image.shape[1] * 3 // 8
print("Maximum bytes to encode:", n_bytes)
#Let's make sure that we have enough image size to encode data
if len(secret_message) > n_bytes:
raise ValueError("Error - insufficient bytes, need bigger image or less data !!")
secret_message = secret_message.decode() + "#####" # using this string as the delimiter
data_index = 0
binary_secret_msg = messageToBinary(secret_message)
data_len = len(binary_secret_msg)
for values in image:
for pixel in values:
# convert RGB values to binary format
r, g, b = messageToBinary(pixel)
# modify the LSB only if there is still data to store
if data_index < data_len:
# red pixel
pixel[0] = int(r[:-1] + binary_secret_msg[data_index], 2)
data_index += 1
if data_index < data_len:
# green pixel
pixel[1] = int(g[:-1] + binary_secret_msg[data_index], 2)
data_index += 1
if data_index < data_len:
# blue pixel
pixel[2] = int(b[:-1] + binary_secret_msg[data_index], 2)
data_index += 1
# break out of the loop if the data is over
if data_index >= data_len:
break
return image
In this method we are performing multiple validations and a couple of tweaks. One of them is adding a delimiter for marking when the data encoding is completed. This is very important in our implementation since while reading the data, it is important that we have to announce the encoded message is over, otherwise it will also include bits which are not part of the data which can lead to error during decryption. We are also calling a new method for binary conversion “messageToBinary”, this is a helper method for converting different types of data to binary during the process.
def messageToBinary(message):
if type(message) == str:
return ''.join([ format(ord(i), "08b") for i in message ])
elif type(message) == bytes or type(message) == np.ndarray:
return [ format(i, "08b") for i in message ]
elif type(message) == int or type(message) == np.uint8:
return format(message, "08b")
else:
raise TypeError("Input type not supported")
Another helper method we have added here for the Encryption process.
def encrypt_message(msg,key):
cipher_suite = Fernet(key)
cipher_text = cipher_suite.encrypt(str.encode(msg))
return cipher_text
That concludes the Encryption and Encoding process, now let’s add other components for Decryption and Decoding.
def decrypt_message(cipher_text,key):
cipher_suite = Fernet(str.encode(key))
plain_text = cipher_suite.decrypt(str.encode(cipher_text))
return plain_text
For decoding,
def decode_text():
# read the image that contains the hidden image
image_name = input("Enter the name of the steganographed image that you want to decode (with extension) :")
image = cv2.imread(image_name) #read the image using cv2.imread()
text = showData(image)
key = input("\n Enter the key to decrypt message : ")
de_text = decrypt_message(text,key)
return de_text
Definition of helper method “showData” where the actual reading of data happening.
def showData(image):
binary_data = ""
for values in image:
for pixel in values:
r, g, b = messageToBinary(pixel) #convert the red,green and blue values into binary format
binary_data += r[-1] #red pixel
binary_data += g[-1] #green pixel
binary_data += b[-1] #blue pixel
# 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:] == "#####": #check if we have reached the delimiter which is "#####"
break
#print(decoded_data)
return decoded_data[:-5] #remove the delimiter to show the original hidden message
Our entire process in the script can be mapped as follows
- Data --> Key Generation --> Encrypt --> Encode --> Image generated & Key shared
- Image shared --> Decode --> Key sharing --> Decrypt --> Print Message
Following are the actual screenshots of the script generated.
Encoding Process |
Decoding Process |
You can find the full code here : https://github.com/sivakumar090/image_stegano
Comments
Post a Comment