Building a Simple Image Encryption Tool Using Python

Emmanuel Kariithi - Oct 9 - - Dev Community

Today, we're going to dive into an exciting project that combines image processing with basic encryption techniques. We'll be exploring a Python program that can encrypt and decrypt images using a simple yet effective method. Let's break it down step by step!

Prerequisites

To follow along, you should have:

  1. Basic knowledge of Python programming.
  2. Python installed on your computer.
  3. Pillow library which is a python imaging Library used for handling images. Use pip install pillow to install.

  4. Tkinter which is a python library used for building graphical user interfaces (GUIs). Use pip install tk to install.

What Does This Program Do?

This program creates a graphical user interface (GUI) that allows users to:

  • Select an image file
  • Choose an output location
  • Enter a seed key
  • Encrypt or decrypt the image

The encryption process shuffles the pixels of the image based on a seed key, making the image unrecognizable. The decryption process reverses this, restoring the original image.

Code Explanation

Importing Required Libraries

import os
from tkinter import Tk, Button, Label, Entry, filedialog, messagebox
from PIL import Image
import random
Enter fullscreen mode Exit fullscreen mode
  • os provides functions to interact with the operating system.
  • tkinter provides GUI elements like buttons, labels, and input fields.
  • PIL (Pillow) allows us to open, manipulate, and save images.
  • random helps us shuffle the pixels in a deterministic way, using a seed.

Seeded Random Generator Function

def get_seeded_random(seed):
    """Returns a seeded random generator."""
    return random.Random(seed)
Enter fullscreen mode Exit fullscreen mode

The get_seeded_random function returns a random object that can shuffle items the same way every time if given the same seed value.

This is key to both encrypting and decrypting images consistently.

Image Encryption

def encrypt_image(input_image_path, output_image_path, seed):
    """Encrypts the image by manipulating pixel values."""
    image = Image.open(input_image_path)
    width, height = image.size
    # Get pixel data as a list
    pixels = list(image.getdata())
    random_gen = get_seeded_random(seed)

    # Create a list of pixel indices
    indices = list(range(len(pixels)))
    # Shuffle the indices using the seeded random generator
    random_gen.shuffle(indices)

    # Reorder pixels based on shuffled indices
    encrypted_pixels = [pixels[i] for i in indices]

    # Create new image
    encrypted_image = Image.new(image.mode, (width, height))
    # Apply encrypted pixels to the new image
    encrypted_image.putdata(encrypted_pixels)
    # Save the encrypted image
    encrypted_image.save(output_image_path)
    return True
Enter fullscreen mode Exit fullscreen mode

In this encrypt_image function:

  • We load the image and extract its pixel data.
  • The pixel order is shuffled using a seeded random generator to ensure the same shuffle order is maintained when decrypting.
  • We create a new image with the shuffled pixel values and save it as the encrypted image.

Image Decryption

def decrypt_image(input_image_path, output_image_path, seed):
    """Decrypts the image by reversing the encryption process."""
    image = Image.open(input_image_path)
    width, height = image.size
    # Get encrypted pixel data as a list
    encrypted_pixels = list(image.getdata())
    random_gen = get_seeded_random(seed)

    # Create a new list to hold pixel indices in their original order
    indices = list(range(len(encrypted_pixels)))
    # Shuffle the indices again to get the original order
    random_gen.shuffle(indices)

    # Create a new image to hold the decrypted data
    decrypted_pixels = [None] * len(encrypted_pixels)

    # Restore original pixels using the shuffled indices
    for original_index, shuffled_index in enumerate(indices):
        decrypted_pixels[shuffled_index] = encrypted_pixels[original_index]

    # Save the decrypted image
    decrypted_image = Image.new(image.mode, (width, height))
    decrypted_image.putdata(decrypted_pixels)
    decrypted_image.save(output_image_path)
    return True
Enter fullscreen mode Exit fullscreen mode

This decrypt_image function works by reversing the encryption process. It:

  • Loads the encrypted image.
  • Uses the same random seed to reshuffle the pixels back into their original order.
  • Creates and saves a new image with the decrypted pixels.

File Selection Functions

def select_input_image():
    """Opens a file dialog to select an input image."""
    input_image_path = filedialog.askopenfilename(title="Select Image")
    input_image_label.config(text=input_image_path)

def select_output_image():
    """Opens a file dialog to select an output image path."""
    output_image_path = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG files", "*.png"),("JPEG files", "*.jpg;*.jpeg"),("All files", "*.*")], title="Save Encrypted/Decrypted Image")

    output_image_label.config(text=output_image_path)
Enter fullscreen mode Exit fullscreen mode

The select_input_image function allows the user to select the image they want to encrypt or decrypt by using a file dialog.

The selected image path is then displayed on the GUI.

Similarly, the select_output_image function allows users to choose where to save the output image.

Encrypt and Decrypt Button Functions

def encrypt():
    input_image_path = input_image_label.cget("text")
    output_image_path = output_image_label.cget("text")
    seed = seed_entry.get()

    if not input_image_path or not output_image_path:
        messagebox.showerror("Error", "Please select input and output images.")
        return

    if encrypt_image(input_image_path, output_image_path, seed):
        messagebox.showinfo("Success", "Image encrypted successfully!")

def decrypt():
    input_image_path = input_image_label.cget("text")
    output_image_path = output_image_label.cget("text")
    seed = seed_entry.get()

    if not input_image_path or not output_image_path:
        messagebox.showerror("Error", "Please select input and output images.")
        return

    if decrypt_image(input_image_path, output_image_path, seed):
        messagebox.showinfo("Success", "Image decrypted successfully!")
Enter fullscreen mode Exit fullscreen mode

The encrypt and decrypt functions:

  • Gets the selected file paths and the seed value entered by the user.
  • Ensures the user has selected both input and output image paths before proceeding.
  • Calls the respective encrypt_image() or decrypt_image() functions and displays success messages once done.

Creating the GUI

root = Tk()
root.title("Image Encryption Tool")

# Create and place widgets
Label(root, text="Select Image to Encrypt/Decrypt:").pack(pady=5)
input_image_label = Label(root, text="No image selected")
input_image_label.pack(pady=5)

Button(root, text="Browse", command=select_input_image).pack(pady=5)

Label(root, text="Output Image Path:").pack(pady=5)
output_image_label = Label(root, text="No output path selected")
output_image_label.pack(pady=5)

Button(root, text="Save As", command=select_output_image).pack(pady=5)

Label(root, text="Enter Seed Key:").pack(pady=5)
seed_entry = Entry(root)
seed_entry.pack(pady=5)

Button(root, text="Encrypt Image", command=encrypt).pack(pady=5)
Button(root, text="Decrypt Image", command=decrypt).pack(pady=5)

root.mainloop()
Enter fullscreen mode Exit fullscreen mode

Labels, buttons, and text entry fields are placed using pack().

The root.mainloop function keeps the window open and responsive to user input.

Program in Action

Image encryption

Conclusion

This program demonstrates how we can manipulate digital images at the pixel level and how we can use pseudo-random number generators for basic encryption tasks.

Happy coding, and stay secure!

. . . . . . . . . .
Terabox Video Player