
Introduction
In this digital age, images play a vital role in our lives. They can hold cherished memories, from your romantic first date to a lovely vacation with your family. Even some people keep private photos as well. We often share our photos with our friends, colleagues, parents, or someone special online. The concern occurs when it comes to cases of private images. There is a chance of leaking or being exploited.
Despite these vulnerabilities, thanks to the advanced technology that offers a solution to this dilemma. Image encryption provides a layer of security by scrambling the visual data of your photos, making them unreadable without the decryption key. You can simply encrypt your private images before sending them to someone or just storing them in a device.
As a solution, Iāve developed an Image encryption application in Python using the Advanced Encryption Standard (AES) in CBC mode and SHA256. In this tutorial, I will share each and every detail of this Python Project also with the Source Code. So, next time when it comes to the turn of a private image, you can share it safely by encrypting that.
The Project Details
This application uses the AES-256 encryption, a symmetric key encryption standard adopted by the U.S. government and widely used for securing sensitive information. During encryption, our application generates a secret key of a specific length (256 bits). This key is crucial for decryption, but itās not the only element needed.
The application also generates an Initialization Vector (IV), which is a random value used alongside the secret key in the encryption process. We used SHA-256, a secure Hash algorithm to generate a unique IV for each encryption.
Think of the IV as an additional layer of security that ensures the same plaintext encrypted with the same key will result in a different ciphertext each time. The IV, along with the encrypted data, is then saved to a separate file.
Important Note: Both the secret key and the IV file are essential for decryption. Make sure to keep them safe and accessible when you need to decrypt your encrypted images. Anyone with access to the secret key and the corresponding IV file can decrypt your data.
This application provides a beautiful user interface that is visually appealing and easy to use. It is developed using the Tkinter library. To make the application more realistic, I used various images in the applicationās sidebar, footer, and buttons. Itās important to place those images within the main project folder.
So, keep following every step till the end, and youāll be able to launch this user-friendly Python project on your system successfully!
Key Elements of AES Encryption
Letās discuss what makes the AES algorithm a popular and stronger encryption method.
- Secret Key: During encryption, the program generates a secret key of a specific length, typically 128, 192, or 256 bits. This key acts as the password for both encryption and decryption. In our program, we will use a 256-bit key to ensure more strong encryption.
- Initialization Vector (IV): The program also generates an Initialization Vector (IV), which is a random value used alongside the secret key in the encryption process. The IV acts as an additional layer of security. Even with the same secret key, encrypting the same image twice will result in different ciphertexts due to the unique IV each time. The IV, along with the encrypted data, is then saved to a separate file.
Requirements and Installations
This project relies on a few external libraries to function correctly. To ensure a smooth setup, make sure you have the following libraries installed on your system:
Install customtkinter
pip install customtkinter==0.3
Install Pillow
pip install pillow
Install pycryptodome
pip install pycryptodome
Setting up the Project File
Create a separate folder for this application and name it āImage-Encryption-Appā. Now download the zip file using the āDownloadā button below. Once youāve done unzip it and place the āImagesā folder inside the main project directory.
Now move on to the next step.
Start writing the code
Choose your favorite text editor (VS Code, PyCharm, or whatever you like) and open the project directory āImage-Encryption-Appā. Now create a Python file there named āmain.pyā.
Import the Necessary Modules
Start writing your code by importing the necessary modules.
import os import io import random import string import numpy as np import customtkinter import tkinter as tk from tkinter import * from tkinter import filedialog from PIL import ImageTk, Image from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Random import get_random_bytes from Crypto.Util import Counter from Crypto.Hash import SHA256, HMAC
Define the Image Encryption Class
Create a class and give it the name ImageEncryption
.
class ImageEncryption:
Create the Main Application Window
In the __init__
method, we create our application window. Here we define the window size, resizable options, menubar, frames, buttons, and all the other widgets to make our application visually appealing.
def __init__(self, root): self.window = root self.window.geometry("920x520") self.window.title('IMAGE ENCRYPTION APP') self.window.resizable(width = False, height = False) # ==============Start Menubar=============== self.menubar = Menu(self.window, bg="#5956BA", fg="white", font=("Montserrat", 9)) # Adding Edit Menu and its sub menus edit = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label='OPEN', menu=edit) edit.add_command(label='ENCRYPT',command=self.open_image_for_encryption) edit.add_command(label='DECRYPT',command=self.open_image_for_decryption) about = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label='ABOUT', menu=about) about.add_command(label='ABOUT', command=self.about) # Exit the Application _exit = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label='EXIT', menu=_exit) _exit.add_command(label='EXIT', command=self.window.destroy) # Configuring the menubar self.window.config(menu=self.menubar) # ===================End of menubar======================= # ===================Start of frames==================== self.frame1 = Frame(self.window, bg='white') self.frame1.place(x=0, y=0, width=720, height=420) self.frame2 = Frame(self.window, bg='white') self.frame2.place(x=0, y=420, width=720, height=100) self.frame3 = Frame(self.window, bg='yellow') self.frame3.place(x=720, y=0, width=200, height=520) # ==================End of frames====================== sidebar_image = Image.open("Images/sidebar.png") footer_image = Image.open("Images/footer.png") encrypt_img = PhotoImage(file='Images/encrypt.png') decrypt_img = PhotoImage(file='Images/decrypt.png') self.browse_iv_img = PhotoImage(file='Images/browse_iv.png') # Displaying sidebar image self.img1 = ImageTk.PhotoImage(sidebar_image) label1 = Label(self.frame3, image=self.img1) label1.pack() # Displaying footer image self.img2 = ImageTk.PhotoImage(footer_image) label2 = Label(self.frame2, image=self.img2) label2.pack() # Encryption Button btn_1 = Button(self.frame3, image=encrypt_img, border=0, cursor="hand2", command=self.pre_encryption) btn_1.place(x=35, y=70) encrypt_btn = customtkinter.CTkButton(master=self.frame3, image=encrypt_img) encrypt_btn.pack(padx= 20, pady=20) # Decryption Button btn_2 = Button(self.frame3, image=decrypt_img, border=0, cursor="hand2", command=self.pre_decryption) btn_2.place(x=35, y=125) decrypt_btn = customtkinter.CTkButton(master=self.frame3, image=decrypt_img) decrypt_btn.pack(padx= 20, pady=20) self.default_values()
Default values
Before starting the complex tasks for our application, we need to define some variables along with setting some default values for them. In the default_values
method we will perform that task.
In the upcoming code, we will call this method multiple times.
def default_values(self): self.encryption_status = False self.decryption_status = False self.image_path = '' self.iv_path = ''
Open an Image
Weāll define two different methods for opening images: open_image_for_encryption
and open_image_for_decryption
.
# For selecting an original image to perform encryption operation def open_image_for_encryption(self): self.image_path = filedialog.askopenfilename(title = "Select an Image", filetypes = (("Image files", "*.jpg *.jpeg *.png"),)) # If an image is selected if len(self.image_path) != 0: # If an encrypted image is selected try: image = Image.open(self.image_path) except: tk.messagebox.showerror(title="Error!", message="Please select a correct image") return self.encryption_status = True # If the last operation was encryption if self.encryption_status and not self.decryption_status: try: self.image_name_label.destroy() self.image_size_label.destroy() self.status_label.destroy() except: pass try: self.key_label.destroy() except: pass # If the last operation was decryption elif self.encryption_status and self.decryption_status: self.clear_screen() try: self.file_name_label.destroy() self.file_status_label.destroy() except: pass try: self.key_entry.destroy() self.btn_3.destroy() except: pass # Displays the selected image (original image) self.display_original_image(self.image_path) else: self.image_path = '' # For selecting an encrypted image to perform decryption def open_image_for_decryption(self): self.image_path = filedialog.askopenfilename(title = "Select an Image", filetypes = (("Image files", "*.jpg *.jpeg *.png"),)) if len(self.image_path) != 0: self.decryption_status = True # If the last operation was decryption if self.decryption_status and not self.encryption_status: self.clear_screen() try: self.file_name_label.destroy() self.file_status_label.destroy() except: pass try: self.key_entry.destroy() self.btn_3.destroy() except: pass # If the last operation was encryption elif self.decryption_status and self.encryption_status: try: self.image_name_label.destroy() self.image_size_label.destroy() self.status_label.destroy() except: pass try: self.key_label.destroy() except: pass # Displays the encrypted image information self.image_information_2() # Displays a dummy image self.display_placeholder_image() else: self.image_path = ''
Resize the Image
After opening an image, we display it. However, the size of the chosen image may vary. It can be landscape, square, or portrait-oriented. Since we have a fixed space to display the image on our application, we have to resize it as per the dimensions.
So, letās define a method named resize_image
to resize the image before displaying it.
# Resizing the image def resize_image(self, image_path): image = Image.open(image_path) width, height = image.size # Determine image type and calculate resize dimensions if width > height: # Landscape new_width = self.frame1.winfo_width() new_height = int(height * (new_width / width)) elif width < height: # Portrait new_height = self.frame1.winfo_height() new_width = int(width * (new_height / height)) else: # Square new_width = 400 new_height = 400 return new_width, new_height
Display the Image
Here, weāll declare three methods for displaying the images on our applicationās window. Letās break down the functionalities of each display method.
- display_original_image: It displays the original image selected for encryption.
- display_decrypted_image: It displays the image that was successfully decrypted from its encrypted form.
- display_placeholder_image: Since encrypted data appears scrambled and unreadable, this method displays a placeholder image after the encryption process is complete. This provides visual feedback to the user without revealing the encrypted image contents.
# Displays the original image before encryption def display_original_image(self, image_path): image = Image.open(image_path) self.clear_screen() self.width, self.height = self.resize_image(self.image_path) resized_image = image.resize((self.width, self.height)) # Create an object of tkinter ImageTk self.image = ImageTk.PhotoImage(resized_image) # Create a new inner frame for the resized image inner_frame = Frame(self.frame1, width=self.width, height=self.height) inner_frame.pack() # Create a label to display the image image_label = Label(inner_frame, image=self.image) image_label.pack() # Displays the image information self.image_information_1() # Displays the decryprted image after decryption operation def display_decrypted_image(self, image_path): self.clear_screen() image = Image.open(image_path) self.width, self.height = self.resize_image(image_path) resized_image = image.resize((self.width, self.height)) # Create an object of tkinter ImageTk self.image = ImageTk.PhotoImage(resized_image) # Create a new inner frame for the resized image inner_frame = Frame(self.frame1, width=self.width, height=self.height) inner_frame.pack() # Create a label to display the image image_label = Label(inner_frame, image=self.image) image_label.pack() self.file_name_label.config(text=f"Image: {os.path.basename(image_path)}") self.file_status_label.config(text=f"Decrypted Image", bg="green") self.key_entry.destroy() self.btn_3.destroy() # Displays a dummy image def display_placeholder_image(self): self.clear_screen() image = Image.open("Images/sample_encrypted_image.png") # Create an object of tkinter ImageTk self.image = ImageTk.PhotoImage(image) # Create a new inner frame for the resized image inner_frame = Frame(self.frame1, width=720, height=420) inner_frame.pack() # Create a label to display the image image_label = Label(inner_frame, image=self.image) image_label.pack()
Display Image Information
To enhance the user experience, weāll display relevant information like the image name and size just below the displayed image.
# Displays the original image information def image_information_1(self): image = Image.open(self.image_path) width, height = image.size self.image_name_label = Label(self.frame2, text=f"Image Name: {os.path.basename(self.image_path)}", font=("Montserrat", 8), bg="#03226F", fg="white") self.image_name_label.place(x=20, y=17) self.image_size_label = Label(self.frame2, text=f"Image Size: {width}x{height}", font=("Montserrat", 8), bg="#03226F", fg="white") self.image_size_label.place(x=20, y=42) self.status_label = Label(self.frame2, text="Image is Opened", font=("Montserrat", 8), bg="#03226F", fg="white") self.status_label.place(x=20, y=67) # Displays the encrypted image information def image_information_2(self): self.clear_screen() self.key_var = StringVar(value='') self.key_entry = Entry(self.frame3, textvariable=self.key_var, font=("Montserrat", 8), width=15) self.key_entry.place(x=81, y=222) self.btn_3 = Button(self.frame3, image=self.browse_iv_img, border=0, cursor="hand2", command=self.browse_IV_file) self.btn_3.place(x=35, y=350) self.browse_iv_btn = customtkinter.CTkButton(master=self.frame3, image=self.browse_iv_img) self.browse_iv_btn.pack(padx= 20, pady=20) self.file_name_label = Label(self.frame2, text=f"Image Name: {os.path.basename(self.image_path)}", font=("Montserrat", 8), bg="#03226F", fg="white") self.file_name_label.place(x=20, y=17) self.file_status_label = Label(self.frame2, text="Image is Opened", font=("Montserrat", 8), bg="#03226F", fg="white") self.file_status_label.place(x=20, y=42)
Select the directory
After you perform encryption and decryption operations you have to select the directory where the resulting image will be saved. Letās declare a method called choose_directory
for this task.
# Opens the filedialog to select a directory for saving resulting image def choose_directory(self): chosen_dir = filedialog.askdirectory() if chosen_dir: return chosen_dir
Encrypt the Image
The below code performs the encryption operation on an image. Letās break down its functionalities:
- After choosing an image when the encrypt button is pressed, the
pre_encryption
is called. - The
pre_encryption
method checks if an image is selected or not. Based on the result, it calls thegenerate_random_
text method to generate a 256-bit (32 bytes) random key value, calls theencrypt_image
method, and generates a secret file (IV file) also for decryption. The encrypted image and the secret file are stored in your chosen directory. Later, it displays the key values after encryption.
Learn: How to Encrypt an Image in Python using AES Algorithm
# Generates random key values def generate_random_text(self): # Available length options: 16, 24, or 32 (You can modify here) length = 32 # Define the character pool containing all desired characters char_pool = string.ascii_uppercase + string.ascii_lowercase + string.digits random_text = ''.join(random.sample(char_pool, length)) return random_text # Performs encryption on images def encrypt_image(self, image_path, output_image_path, iv_path, key): image = Image.open(image_path) # Generate a random IV iv = get_random_bytes(AES.block_size) # Get a unique identifier from the filename image_hash = SHA256.new(os.path.basename(image_path).encode("utf-8")).hexdigest() # Combine key-specific value with random string (nonce) using HMAC key_specific = HMAC.new(key, msg=image_hash.encode("utf-8"), digestmod=SHA256).digest() unique_iv = HMAC.new(key_specific, msg=os.urandom(16), digestmod=SHA256).digest()[:16] # Convert the image to bytes img_byte_array = io.BytesIO() image.save(img_byte_array, format=image.format) img_bytes = img_byte_array.getvalue() # Initialize AES cipher cipher = AES.new(key, AES.MODE_CBC, unique_iv) # Encrypt the image data with padding padded_data = pad(img_bytes, AES.block_size) encrypted_data = iv + cipher.encrypt(padded_data) # Write the encrypted data with IV to the output image file with open(output_image_path, 'wb') as f: f.write(encrypted_data) # Saving the iv file with open(iv_path, 'wb') as f: f.write(unique_iv) self.decryption_status = False # Performs pre-encryprion tasks def pre_encryption(self): if self.image_path == '': tk.messagebox.showerror(title="Image Missing", message="Please select an image") else: key = self.generate_random_text() key = bytes(key, encoding="utf-8") key_str = key[0:].decode("utf-8") chosen_dir = self.choose_directory() filename = os.path.basename(self.image_path) image_name = filename.split('.')[0] output_image_path = f"{chosen_dir}/{image_name}_encrypted.jpg" iv_path = f"{chosen_dir}/{image_name}_encrypted.iv" self.encrypt_image(self.image_path, output_image_path, iv_path, key) self.status_label.config(text="Image is encrypted", bg="green") self.key_label_var = StringVar() self.key_label = Entry(self.frame2, textvariable=self.key_label_var, font=("Montserrat", 8), width=20, bg="#03226F", fg="white") self.key_label.insert(0, f"{key_str}") self.key_label.place(x=180, y=17) self.clear_screen() self.image_path = ''
The encryption process follows the AES-256 encryption in CBC mode. Hereās a breakdown of the steps:
- First, it opens the image using the Pillow library.
- Generates a random Initialization Vector (IV) of the same size as the AES block size (usually 16 bytes).
- Calculates a hash (unique identifier) of the image filename using SHA-256.
- Then it uses HMAC to combine the key-specific value (derived from the key and filename hash) with another random string to create a unique IV for this specific encryption.
- Now it converts the image data into a byte array.
- Initializes an AES cipher object with the provided key, CBC mode, and the unique IV.
- Next, it pads the image byte array to ensure its length is a multiple of the AES block size.
- Encrypts the padded data using the AES cipher.
- Combines the encrypted data with the IV and writes it to the output image file.
- Finally, it saves the unique IV in a separate file for decryption.
Select the secret file
During encryption, our application generates a secret file (Initialization Vector) for an additional layer of security. You must select it during decryption. Letās declare a method called browse_IV_file
for selecting the IV file through Tkinter filedialog.
# Selecting the iv file (secret file) def browse_IV_file(self): self.iv_path = filedialog.askopenfilename(title = "Select the IV File", filetypes=(("IV File", "*.iv"),))
Decrypt the Image
Letās decrypt the image that was previously encrypted with the AES algorithm.
# Performs decryption on images def decrypt_image(self, input_image_path, output_image_path, key, iv_path): self.output_image_path = output_image_path # Read the IV from the separate file with open(iv_path, 'rb') as f: unique_iv = f.read() # Read the encrypted data with open(input_image_path, 'rb') as f: encrypted_data = f.read() try: # Separate the IV from the encrypted data iv = encrypted_data[:AES.block_size] encrypted_data = encrypted_data[AES.block_size:] # Initialize AES cipher cipher = AES.new(key, AES.MODE_CBC, unique_iv) # Decrypt the image data decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size) try: # Convert decrypted bytes to image decrypted_image = Image.open(io.BytesIO(decrypted_data)) except: tk.messagebox.showerror(title="Wrong IV File", message="Wrong IV File") self.clear_screen() self.file_name_label.destroy() self.file_status_label.destroy() self.key_entry.destroy() self.btn_3.destroy() return # Save the decrypted image decrypted_image.save(self.output_image_path, format=decrypted_image.format) self.display_decrypted_image(self.output_image_path) self.encryption_status = False except ValueError: tk.messagebox.showerror(title="Incorrect Key", message="Incorrect key value") self.clear_screen() self.file_name_label.destroy() self.file_status_label.destroy() self.key_entry.destroy() self.btn_3.destroy() # Performs pre-decryption tasks def pre_decryption(self): if self.image_path == '': tk.messagebox.showerror(title="Image Missing", message="Please select an image") return elif self.key_entry.get()=='': tk.messagebox.showerror(title="Key Missing", message="Please enter the key") return elif self.iv_path == '': tk.messagebox.showerror(title="IV File Missing", message="Please select the IV file") return else: key = self.key_var.get() key = bytes(key, encoding="utf-8") chosen_dir = self.choose_directory() filename = os.path.basename(self.image_path) image_name = filename.split('.')[0] output_image_path = f"{chosen_dir}/{image_name}_recovered.jpg" self.decrypt_image(self.image_path, output_image_path, key, self.iv_path) self.image_path = '' self.iv_path = ''
Here is the breakdown of the above code:
- Reads the IV (Initialization Vector) from the separate file.
- Reads the encrypted data from the encrypted image file.
- Separates the IV from the actual encrypted data.
- Sets up the AES decryption with the key and the IV.
- Decrypts the encrypted data using AES.
- Converts the decrypted data back into an image format.
- Saves the decrypted image to a new file.
Clear the Screen
Frame 1 (where the image is displayed) needs to be cleared repeatedly throughout the program. So, weāll define a method called clear_screen
to clear the widgets present in Frame 1.
# Clears the Frame 1 def clear_screen(self): for widget in self.frame1.winfo_children(): widget.destroy()
About the Application
Itās a common practice to show the details of an application in a separate segment. In our application, we will do so. Letās declare a method to display a short intro about our application.
# Displayes about section of the application def about(self): about_frame = Frame(self.window) about_frame.place(x=290, y=2, width=340, height=180) # Create labels with formatted text app_name_label = Label(about_frame, text="IMAGE ENCRYPTOR v1.0", font=("Montserrat", 12, "bold")) app_name_label.pack(pady=5) features_label = Label(about_frame, text="**Features**", font=("Montserrat", 8, "bold")) features_label.place(x=15, y=35) point_1 = Label(about_frame, text="* Encrypts images using a secure algorithm", font=("Montserrat", 8)) point_1.place(x=15, y=50) point_2 = Label(about_frame, text="* Decrypts encrypted images with the corresponding key", font=("Montserrat", 8)) point_2.place(x=15, y=65) developer_info = Label(about_frame, text="**Developed By: Subhankar Rakshit", font=("Montserrat", 8, "bold")) developer_info.place(x=15, y=85) # License information license_label = Label(about_frame, text="**License: Apache License, Version 2.0", font=("Montserrat", 8, "bold")) license_label.place(x=15, y=105) # Close button close_button = Button(about_frame, text="CLOSE", font=("Montserrat", 7), cursor="hand2", command=about_frame.destroy) close_button.place(x=135, y=149)
Initialize the Application
Letās create an instance of the ImageEncryption
class to create the graphical user interface and start the main loop.
if __name__ == "__main__": root = Tk() obj = ImageEncryption(root) root.mainloop()
In the above code, we check if the script is being run as the main program (__name__ == ā__main__ā
). This ensures that the code inside this block only runs when the script is executed, not when itās imported as a module.
How to use this application
Follow the steps below to operate this Image encryptor application properly:
- Open the Image Encryptor Program (app.py).
- Click the āOpenā button from the menubar and select āEncryptā or āDecryptā.
- If you choose āEncryptā
- Select the image.
- After opening the image, press the āEncryptā Button.
- Choose the destination folder where you want to save the encrypted image.
- Note down or copy the key value. Keep the IV file safe as well.
- If you choose āDecryptā
- Select the image.
- Enter the key value.
- Select the IV file.
- Press the āDecryptā button.
- Choose the destination folder where you want to save the decrypted image.
Output
Watch the entire video to understand how this Image Encrypter App works:
Summary
In this tutorial, we create an image encryption and decryption application using Python and tkinter library. The program uses the AES algorithm in CBC mode and generates one unique key and Initialization Vector for every encryption. The key values are 256 bits and are chosen randomly.
Multiple images are used in this application to make the user interface more appealing. The entire programming of this application contains so many tricky logic. Please follow every single step from the beginning to the end to launch the application successfully on your system.
For any queries or feedback, leave your thoughts in the comments. I would love to hear from you.
Want more interesting Python Projects? Visit our separate page packed with unique ideas. Here are a few instances to spark your interest:
- Create a Meditation App in Python with Tkinter
- Create a YouTube Downloader in Python using Tkinter
- Build a Face Recognition Login System using Python
- Encrypt and Decrypt PDF Files in Python using Tkinter
Happy Coding!