Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
Imagine if you could hide a secret message within a photo, and no one could tell by just looking at it.
This is the magic of steganography—a powerful technique that allows us to embed secret information into ordinary media files like images, audio, or video.
The concept has existed for centuries, but modern digital steganography makes it possible to hide data within digital files in ways that are virtually undetectable.
In this article, we’ll explore image-based steganography by creating a simple, beginner-friendly program in Python.
This program will allow you to encode and decode hidden messages in images by tweaking pixel values, demonstrating how steganography works at a practical level.
Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
Contents
Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
What you’ll build:
Before we start building let’s understand the concept.
Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
What is Steganography?
Steganography, in the simplest terms, is the art of concealing a message within another medium so that it remains invisible to an unsuspecting observer.
Unlike encryption, which scrambles data to make it unreadable, steganography keeps the data “in plain sight” by hiding it inside other data.
Example Use Cases of Steganography:
- Private Communication: Send secret messages without drawing attention.
- Watermarking: Embed a hidden watermark in images to protect intellectual property.
- Forensics and Cybersecurity: Track data usage or embed secret security codes.
Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
How Image Steganography Works
In digital images, each pixel is composed of three color channels: Red, Green, and Blue (RGB).
Each channel can hold a value between 0 and 255, which represents its intensity. By making tiny changes to these RGB values, we can hide binary data within the image without noticeably altering its appearance.
➡️➡️Click Here for a Step-by-Step Guide to Encoding a Message (e.g., “Hi”) in an Image
Let’s see how encoding works by hiding the message “Hi” within the pixels of an image.
Step 1: Convert the Message to Binary
To store our message in binary form, we first need to convert each character to its ASCII value, and then to binary.
For “Hi”:
- Character ‘H’: ASCII value is 72, binary equivalent is
01001000
. - Character ‘i’: ASCII value is 105, binary equivalent is
01101001
.
So, “Hi” becomes:
01001000 01101001
Each character is represented by 8 bits, so the message “Hi” has 16 bits total.
Step 2: Select Pixels to Store the Data
To hide our binary data, we’ll modify the Least Significant Bit (LSB) of each color component in the selected pixels.
The LSB is the last bit in a binary number (e.g., in 11000010
, the last bit is 0
). Changing this bit from 0 to 1 or vice versa only slightly alters the color, which remains visually indistinguishable to the human eye.
Example Pixels: Let’s say we have the following three pixels:
Pixel 1: (27, 64, 164)
Pixel 2: (248, 244, 194)
Pixel 3: (174, 246, 250)
Step 3: Embed the Data in Pixels
Now, we’ll embed our binary data into these pixels by modifying the LSB of each color value to match our binary message. For example:
Binary for ‘H’: 01001000
- First Pixel: RGB values are
(27, 64, 164)
- 27 (binary:
00011011
) → Change LSB to0
for0
, making it00011010
(26). - 64 (binary:
01000000
) → Change LSB to1
for1
, making it01000001
(63). - 164 (binary:
10100100
) → Keep LSB as0
for0
.
- 27 (binary:
- Modified Pixel 1:
(26, 63, 164)
Binary for ‘i’: 01101001
- Second Pixel: RGB values are
(248, 244, 194)
- 248 (binary:
11111000
) → Keep LSB as0
. - 244 (binary:
11110100
) → Change LSB to1
, making it11110101
(243). - 194 (binary:
11000010
) → Keep LSB as0
.
- 248 (binary:
- Modified Pixel 2:
(248, 243, 194)
After encoding, our modified pixels look like this:
Original Pixels -> Modified Pixels
(27, 64, 164) -> (26, 63, 164)
(248, 244, 194) -> (248, 243, 194)
Step 4: Finalize with a “Stop” Signal
To signal the end of the message, we can use a pixel with a specific pattern, like setting the LSBs to a known value.
Let’s now start building our encoder and decoder.
Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
Step 1: Import Necessary Libraries
➡️Click For Code
import customtkinter as ctk
from PIL import Image
from typing import List, Iterator, Tuple
- customtkinter as ctk: This imports the
customtkinter
library asctk
, a custom version of thetkinter
library, often used for creating GUIs. - from PIL import Image: This imports the
Image
class from the Python Imaging Library (PIL), allowing us to open, modify, and save images. - from typing import List, Iterator, Tuple: These imports define specific data types (List, Iterator, Tuple) to make the code more readable and understandable.
Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
Step 2: Creating the SteganographyApp Class
➡️Click For Code
class SteganographyApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Pixel Crypt")
self.geometry("700x400")
self.build()
- SteganographyApp(ctk.CTk): This creates a class named
SteganographyApp
, which inherits fromctk.CTk
, making it a GUI window. - init(): The initializer method runs as soon as an instance of this class is created.It sets up the app title, window size, and then calls the
build
method to lay out the GUI components.
Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
Step 3: Building the GUI Layout
➡️Click For Code
def build(self) -> None:
"""Lays out the GUI elements for encoding and decoding operations."""
# Encoding Section
self.encode_label = ctk.CTkLabel(self, text="Encode Message into Image")
self.encode_label.grid(row=0, column=0, padx=20, pady=10)
self.image_path_entry = ctk.CTkEntry(self, placeholder_text="Enter image path")
self.image_path_entry.grid(row=1, column=0, padx=20, pady=5)
self.message_entry = ctk.CTkEntry(self, placeholder_text="Enter message to encode")
self.message_entry.grid(row=2, column=0, padx=20, pady=5)
self.resulting_image_entry = ctk.CTkEntry(self, placeholder_text="Enter resulting image path")
self.resulting_image_entry.grid(row=3, column=0, padx=20, pady=5)
self.encode_button = ctk.CTkButton(self, text="Encode", command=self.encode_image)
self.encode_button.grid(row=4, column=0, padx=20, pady=10)
self.encode_result_label = ctk.CTkLabel(self, text="")
self.encode_result_label.grid(row=5, column=0, padx=20, pady=5)
# Decoding Section
self.decode_label = ctk.CTkLabel(self, text="Decode Message from Image")
self.decode_label.grid(row=0, column=1, padx=20, pady=10)
self.decode_image_path_entry = ctk.CTkEntry(self, placeholder_text="Enter image path to decode")
self.decode_image_path_entry.grid(row=1, column=1, padx=20, pady=5)
self.decode_button = ctk.CTkButton(self, text="Decode", command=self.decode_image)
self.decode_button.grid(row=2, column=1, padx=20, pady=10)
self.decode_result_label = ctk.CTkLabel(self, text="")
self.decode_result_label.grid(row=3, column=1, padx=20, pady=5)
Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
➡️Click For Explanation
The build
method adds all elements to the GUI:
- Encode Section:
self.encode_label
: Label for the encoding section.self.image_path_entry
: Entry box for the user to enter the path of the image they want to encode a message into.self.message_entry
: Entry box for entering the message to encode.self.resulting_image_entry
: Entry box for entering the path where the resulting encoded image will be saved.self.encode_button
: Button that starts the encoding process when clicked, calling theencode_image
method.self.encode_result_label
: Label to display the result of the encoding process (success or error message).
- Decode Section:
self.decode_label
: Label for the decoding section.self.decode_image_path_entry
: Entry box for entering the path of the image to decode.self.decode_button
: Button that starts the decoding process, calling thedecode_image
method.self.decode_result_label
: Label to display the decoded message or any error.
Step 4: Converting Data to Binary with genData
➡️Click For Code
def genData(self, data: str) -> List[str]:
"""
Convert encoding data into 8-bit binary form using ASCII values of characters.
"""
return [format(ord(i), '08b') for i in data]
Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
genData: Converts each character in the message into an 8-bit binary string (using ASCII values). This is important for encoding the message into image pixels.
Step 5: Modifying Pixels with modPix
The modPix
method is where the pixel data of an image is modified to hide the binary representation of the message within the image.
This is the essence of steganography in this application. Let’s break down how it works:
➡️Click For Code
def modPix(self, pix: Iterator[Tuple[int, int, int]], data: str) -> Iterator[Tuple[int, int, int]]:
"""
Modify pixels according to the 8-bit binary data of the message.
Parameters:
pix (Iterator[Tuple[int, int, int]]): An iterator of pixel tuples from the image.
data (str): The message to be encoded.
Yields:
Iterator[Tuple[int, int, int]]: Modified pixels as tuples.
"""
datalist = self.genData(data)
lendata = len(datalist)
imdata = iter(pix)
for i in range(lendata):
pix = [value for value in imdata.__next__()[:3] +
imdata.__next__()[:3] +
imdata.__next__()[:3]]
for j in range(0, 8):
if (datalist[i][j] == '0' and pix[j] % 2 != 0):
pix[j] -= 1
elif (datalist[i][j] == '1' and pix[j] % 2 == 0):
pix[j] = pix[j] - 1 if pix[j] != 0 else pix[j] + 1
if (i == lendata - 1):
if (pix[-1] % 2 == 0):
pix[-1] = pix[-1] - 1 if pix[-1] != 0 else pix[-1] + 1
else:
if (pix[-1] % 2 != 0):
pix[-1] -= 1
pix = tuple(pix)
yield pix[0:3]
yield pix[3:6]
yield pix[6:9]
Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
- Generate Binary Data: First, it uses
genData(data)
to convert the message (text) into a list of binary strings. Each character in the message is converted to an 8-bit binary representation. - Pixel Iterator Setup:
imdata = iter(pix)
creates an iterator from the image’s pixel data, which allows us to go through each pixel one by one. - Loop Through Message Bits: The loop
for i in range(lendata)
iterates through each character’s binary representation in the message. - Read 9 Pixels at a Time: The code
pix = [value for value in imdata.__next__()[:3] + imdata.__next__()[:3] + imdata.__next__()[:3]]
reads 9 pixel values (27 color channels in total). These pixels will be modified to store a single character from the message. - Modify Pixels Based on Message:
- For each bit in the binary string, the pixel values are modified to represent the bit (0 or 1).
- If the bit is
'0'
, it ensures that the pixel’s color value is even (even values encode 0). - If the bit is
'1'
, it makes the color value odd (odd values encode 1).
- Handle End of Message: If it’s the last character (end of the message), the last pixel in this 9-pixel set is adjusted to make its value odd. This odd pixel signals the end of the message during decoding. If it’s not the end, this pixel is kept even.
- Yield Modified Pixels: Each modified set of 9 pixels is then yielded back to be encoded into the image.
Step 6: Encoding Data into the Image with encode_enc
The encode_enc
method writes the modified pixels (from modPix
) into a copy of the original image to create a new, encoded image.
➡️Click For Code
def encode_enc(self, newimg: Image.Image, data: str) -> None:
"""
Encode the data into the image by modifying its pixels.
Parameters:
newimg (Image.Image): The image where data will be encoded.
data (str): The message to be encoded.
"""
w = newimg.size[0]
(x, y) = (0, 0)
for pixel in self.modPix(newimg.getdata(), data):
newimg.putpixel((x, y), pixel)
if x == w - 1:
x = 0
y += 1
else:
x += 1
Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
- Get Image Dimensions: It retrieves the width of the image (how many pixels per row) to keep track of where to place modified pixels as it moves across rows.
- Initialize Coordinates:
(x, y) = (0, 0)
starts encoding at the top-left corner. - Loop Through Modified Pixels: The
for pixel in self.modPix(newimg.getdata(), data)
loop goes through each modified pixel set (frommodPix
) and places them in the new image.newimg.putpixel((x, y), pixel)
sets each modified pixel at the current(x, y)
coordinates in the new image.
- Coordinate Update: It moves to the next pixel in the row until reaching the end, at which point it resets
x
to 0 and moves down to the next row (y += 1
).
This completes the encoding process by filling the new image with pixels modified to hide the message.
Step 7: Encoding Button Functionality with encode_image
The encode_image
method is triggered when the user clicks the “Encode” button. This method handles user inputs, calls encoding functions, and updates the GUI.
➡️Click For Code
def encode_image(self) -> None:
"""Handles the encoding process, saves the resulting image, and updates the GUI with a success message."""
try:
img_path = self.image_path_entry.get()
message = self.message_entry.get()
result_img_path = self.resulting_image_entry.get()
image = Image.open(img_path, 'r')
if len(message) == 0:
raise ValueError("Message cannot be empty")
new_img = image.copy()
self.encode_enc(new_img, message)
new_img.save(result_img_path)
self.encode_result_label.configure(text="Message encoded successfully!")
except Exception as e:
self.encode_result_label.configure(text=f"Error: {e}")
Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
- Get Inputs from GUI:
- Retrieves the image path from
self.image_path_entry.get()
. - Gets the message to be encoded and the path for saving the encoded image.
- Retrieves the image path from
- Load the Image: Opens the image file at
img_path
. - Check Message: If the message is empty, it raises an error since encoding an empty message isn’t allowed.
- Create and Encode New Image: It makes a copy of the image to avoid altering the original and passes it to
self.encode_enc(new_img, message)
, which encodes the message intonew_img
. - Save and Display Result: The new, encoded image is saved, and if everything goes well, the GUI is updated to display “Message encoded successfully!”. If an error occurs (e.g., if the image path is invalid), it shows the error message in the GUI.
Step 8: Decoding Button Functionality with decode_image
This method is activated when the user clicks the “Decode” button to extract a hidden message from an image.
➡️Click For Code
def decode_image(self) -> None:
"""Handles the decoding process and updates the GUI with the decoded message."""
try:
img_path = self.decode_image_path_entry.get()
image = Image.open(img_path, 'r')
decoded_message = self.decode_data(image)
self.decode_result_label.configure(text=f"Decoded Message: {decoded_message}")
except Exception as e:
self.decode_result_label.configure(text=f"Error: {e}")
Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
- Get Image Path: Fetches the path of the image to decode from
self.decode_image_path_entry.get()
. - Open Image: Loads the specified image file.
- Decode Data: It calls
self.decode_data(image)
, which will return the hidden message from the image. - Display Result: Updates the GUI with the decoded message. If there’s an error (e.g., if the file path is incorrect), it shows the error message instead.
Step 9: Decoding the Message with decode_data
The decode_data
method extracts the hidden message from the pixels of an encoded image.
➡️Click For Code
def decode_data(self, image: Image.Image) -> str:
"""
Decode a hidden message from an image file.
Parameters:
image (Image.Image): The image containing the hidden message.
Returns:
str: The decoded message from the image.
"""
data = ''
imgdata = iter(image.getdata())
while True:
pixels = [value for value in imgdata.__next__()[:3] +
imgdata.__next__()[:3] +
imgdata.__next__()[:3]]
binstr = ''.join(['0' if i % 2 == 0 else '1' for i in pixels[:8]])
data += chr(int(binstr, 2))
if pixels[-1] % 2 != 0:
return data
Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
- Initialize an Empty Message: It starts with an empty string
data
where it will build up the decoded message. - Loop Through Pixels:
- Uses
imgdata = iter(image.getdata())
to get an iterator over the pixel data of the image.
- Uses
- Read 9 Pixels at a Time: Like in encoding, it reads 9 pixels at a time, using the first 8 pixels to interpret binary bits for each character and the 9th pixel as an end-of-message marker.
- Extract Binary Bits:
- For each color value in the first 8 pixels, it reads whether the value is even or odd.
- Builds up an 8-bit binary string (
binstr
) where0
represents even and1
represents odd.
- Convert Binary to Character:
- Converts
binstr
to an integer and then to its ASCII character form, adding this character todata
.
- Converts
- End of Message Check: The last pixel in each 9-pixel set acts as a flag. If its color value is odd, the message has ended, so it returns
data
. If not, it continues reading pixels.
This method completes the process by returning the full hidden message extracted from the image.
Step 10: Running the Application
➡️Click For Code
if __name__ == "__main__":
app = SteganographyApp()
app.mainloop()
Pixel Crypt Encoding Secrets Within Images using Python CustomTkinter
Main Loop: If this file is executed directly, this block creates an instance of SteganographyApp
and starts the GUI, allowing the user to interact with the encoding and decoding functions.
Ending Challenge
Now that you’ve built a basic steganography tool, it’s time to take it up a notch! Try adding one or more of these advanced features to your project:
- Encrypt Your Message: Add an encryption layer to your encoding process so that even if someone decodes the image, they would still need a password to reveal the message. Look into simple encryption methods like Caesar Cipher or advanced ones like AES encryption.
- Increase Encoding Capacity: Explore ways to encode larger messages by hiding data in more image channels (e.g., alpha channel for images with transparency) or by reducing the color depth to free up bits for data.
- Detect Manipulation: Add functionality to check if the image has been tampered with (e.g., compressed or resized) before attempting to decode. This would prevent errors or issues in retrieving the message.
Full Source Code
Once you’ve nailed the challenge, share your updated version here in the project’s repository GitHub! Let’s grow the project together and learn as a community.
The complete source code is in the GitHub link provided above.