Building a Secure File Encryption/Decryption Tool with Python: A Beginner’s Guide

Hy guys in today’s article I am going to Create a File Encryption/Decryption Tool using symmetric-key cryptography algorithms like AES (Advanced Encryption Standard) involves several key steps. In this tool, users will be prompted to input a passphrase to encrypt and decrypt files. Let’s walk through the process step by step, with detailed explanations and code examples.

Step 1: Setting Up the Environment

First, we need to ensure that the required libraries are installed. We’ll use the cryptography library for AES encryption and decryption. Install it using pip:

pip install cryptography

Step 2: Importing Necessary Libraries

We start by importing the necessary modules from the cryptography library, as well as other standard modules for handling file operations and user input.

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
import os
import base64
from getpass import getpass

Step 3: Generating a Key from a Passphrase

To use AES encryption, we need a fixed-length key. We’ll derive this key from a passphrase provided by the user using a key derivation function (KDF). PBKDF2HMAC is a common KDF that we will use.

def derive_key(passphrase, salt):
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=100000,
        backend=default_backend()
    )
    key = kdf.derive(passphrase.encode())
    return key

Step 4: Encrypting a File

For encryption, we need to:

  1. Read the file’s contents.
  2. Pad the contents to make their length a multiple of the block size (16 bytes for AES).
  3. Encrypt the padded contents using AES in CBC mode.
  4. Write the salt, IV, and encrypted contents to a new file.

Here’s the encryption function:

def encrypt_file(file_path, passphrase):
    salt = os.urandom(16)
    key = derive_key(passphrase, salt)
    iv = os.urandom(16)
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    encryptor = cipher.encryptor()
    padder = padding.PKCS7(algorithms.AES.block_size).padder()

    with open(file_path, 'rb') as f:
        file_data = f.read()
    
    padded_data = padder.update(file_data) + padder.finalize()
    encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
    
    with open(file_path + '.enc', 'wb') as f:
        f.write(salt + iv + encrypted_data)
    
    print(f"File encrypted and saved as {file_path}.enc")

Step 5: Decrypting a File

For decryption, we need to:

  1. Read the encrypted file’s contents.
  2. Extract the salt, IV, and encrypted contents.
  3. Derive the key using the same passphrase and salt.
  4. Decrypt the contents using AES in CBC mode.
  5. Unpad the decrypted contents.
  6. Write the decrypted contents to a new file.

Here’s the decryption function:

def decrypt_file(file_path, passphrase):
    with open(file_path, 'rb') as f:
        file_data = f.read()
    
    salt = file_data[:16]
    iv = file_data[16:32]
    encrypted_data = file_data[32:]
    key = derive_key(passphrase, salt)
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    decryptor = cipher.decryptor()
    unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
    
    decrypted_padded_data = decryptor.update(encrypted_data) + decryptor.finalize()
    decrypted_data = unpadder.update(decrypted_padded_data) + unpadder.finalize()
    
    with open(file_path[:-4], 'wb') as f:
        f.write(decrypted_data)
    
    print(f"File decrypted and saved as {file_path[:-4]}")

Step 6: Main Function to Run the Tool

The main function will prompt the user to choose between encrypting and decrypting a file, input the file path and passphrase, and then call the appropriate function.

def main():
    print("Welcome to the File Encryption/Decryption Tool")
    choice = input("Would you like to (E)ncrypt or (D)ecrypt a file? ").strip().lower()
    
    if choice not in ['e', 'd']:
        print("Invalid choice. Please select 'E' to encrypt or 'D' to decrypt.")
        return
    
    file_path = input("Enter the file path: ").strip()
    passphrase = getpass("Enter the passphrase: ").strip()
    
    if choice == 'e':
        encrypt_file(file_path, passphrase)
    elif choice == 'd':
        decrypt_file(file_path, passphrase)

if __name__ == "__main__":
    main()

Explanation of the Code

Importing Libraries

We import necessary modules from the cryptography library for encryption and decryption tasks. We also import standard libraries for file handling and getting user input securely.

Key Derivation

The derive_key() function takes a passphrase and a salt to derive a fixed-length key using PBKDF2HMAC. This ensures that the same passphrase always generates the same key when combined with the same salt, which is crucial for both encryption and decryption.

Encrypting Files

The encrypt_file() function starts by generating a random salt and IV. The salt is used in the key derivation process to add randomness. The IV is used in AES CBC mode to ensure that identical plaintext blocks result in different ciphertext blocks. The function reads the file, pads the data, encrypts it, and then writes the salt, IV, and encrypted data to a new file with an .enc extension.

Decrypting Files

The decrypt_file() the function reads the encrypted file, extracts the salt and IV, and derives the key using the same passphrase and salt. It decrypts the data, removes the padding, and writes the decrypted data to a new file by removing the .enc extension.

Main Function

The main() function prompts the user to choose between encryption and decryption, gets the file path and passphrase, and calls the appropriate function. The use of getpass ensures that the passphrase is entered securely without being displayed on the screen.

Testing and running the code:

let’s go through the process of testing this code step by step. We’ll simulate running the code in a Python environment and observe the responses for various actions like encryption and decryption.

Complete Code for File Encryption/Decryption Tool

Here is the full code that we will test:

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
import os
import base64
from getpass import getpass
import sys

# Function to derive a key from the passphrase
def derive_key(passphrase, salt):
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=100000,
        backend=default_backend()
    )
    key = kdf.derive(passphrase.encode())
    return key

# Function to encrypt a file
def encrypt_file(file_path, passphrase):
    salt = os.urandom(16)
    key = derive_key(passphrase, salt)
    iv = os.urandom(16)
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    encryptor = cipher.encryptor()
    padder = padding.PKCS7(algorithms.AES.block_size).padder()

    with open(file_path, 'rb') as f:
        file_data = f.read()
    
    padded_data = padder.update(file_data) + padder.finalize()
    encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
    
    with open(file_path + '.enc', 'wb') as f:
        f.write(salt + iv + encrypted_data)
    
    print(f"File encrypted and saved as {file_path}.enc")

# Function to decrypt a file
def decrypt_file(file_path, passphrase):
    with open(file_path, 'rb') as f:
        file_data = f.read()
    
    salt = file_data[:16]
    iv = file_data[16:32]
    encrypted_data = file_data[32:]
    key = derive_key(passphrase, salt)
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    decryptor = cipher.decryptor()
    unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
    
    decrypted_padded_data = decryptor.update(encrypted_data) + decryptor.finalize()
    decrypted_data = unpadder.update(decrypted_padded_data) + unpadder.finalize()
    
    with open(file_path[:-4], 'wb') as f:
        f.write(decrypted_data)
    
    print(f"File decrypted and saved as {file_path[:-4]}")

# Main function to run the tool
def main():
    print("Welcome to the File Encryption/Decryption Tool")
    choice = input("Would you like to (E)ncrypt or (D)ecrypt a file? ").strip().lower()
    
    if choice not in ['e', 'd']:
        print("Invalid choice. Please select 'E' to encrypt or 'D' to decrypt.")
        return
    
    file_path = input("Enter the file path: ").strip()
    passphrase = getpass("Enter the passphrase: ").strip()
    
    if choice == 'e':
        encrypt_file(file_path, passphrase)
    elif choice == 'd':
        decrypt_file(file_path, passphrase)

if __name__ == "__main__":
    main()

Step-by-Step Testing

Step 1: Running the Tool

When you run the script, it should welcome you and ask if you want to encrypt or decrypt a file.

Console Output:

Welcome to the File Encryption/Decryption Tool
Would you like to (E)ncrypt or (D)ecrypt a file? 

User Input:

e

Step 2: Encrypting a File

After choosing to encrypt a file, the tool will ask for the file path and passphrase. For this example, we’ll assume the file is named test.txt and contains the text “This is a test file.”

Console Output:

Enter the file path: 

User Input:

test.txt

Console Output:

Enter the passphrase: 

User Input:

mysecretpass

The tool reads the file, encrypts it, and saves the encrypted file as test.txt.enc.

Console Output:

File encrypted and saved as test.txt.enc

Step 3: Decrypting a File

Now, let’s decrypt the file we just encrypted. Run the script again and choose to decrypt a file.

Console Output:

Welcome to the File Encryption/Decryption Tool
Would you like to (E)ncrypt or (D)ecrypt a file? 

User Input:

d

The tool will ask for the file path and passphrase.

Console Output:

Enter the file path: 

User Input:

test.txt.enc

Console Output:

Enter the passphrase: 

User Input:

mysecretpass

The tool reads the encrypted file, decrypts it, and saves the decrypted file as test.txt.

Console Output:

File decrypted and saved as test.txt

Detailed Explanation of Responses

  1. Welcome Message and User Choice:
    • The tool starts by welcoming the user and prompting them to choose between encryption and decryption.
    • This step ensures the user is aware of the tool’s capabilities and sets up the appropriate flow based on their choice.
  2. Encrypting a File:
    • After the user chooses to encrypt a file, the tool asks for the file path and a passphrase.
    • The file is read in binary mode, and padding is added to make its length a multiple of the AES block size.
    • The tool uses AES in CBC mode for encryption, with a randomly generated salt and IV to enhance security.
    • The encrypted data is saved in a new file with a .enc extension, indicating it’s encrypted.
  3. Decrypting a File:
    • When the user chooses to decrypt a file, the tool again asks for the file path and passphrase.
    • The tool reads the encrypted file, extracts the salt and IV, and derives the key using the same passphrase and salt.
    • It decrypts the data and removes the padding, saving the decrypted contents back to a file without the .enc extension.

This detailed walkthrough demonstrates how to use the file encryption/decryption tool, providing clarity on how the tool interacts with the user and processes files securely using AES encryption. The tool is robust, user-friendly, and ensures that files can be encrypted and decrypted effectively with a user-provided passphrase.

Leave a Comment