Secure File Encryption Tool Python

👤 Sharing: AI
```python
import os
import cryptography
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.backends import default_backend
import base64


def generate_key(password, salt=None):
    """
    Generates a strong encryption key from a user-provided password using PBKDF2.
    If salt is not provided, a random one is generated.

    Args:
        password (str): The user's password.  This is the weak point, so encourage strong passwords!
        salt (bytes, optional):  A salt value. If None, a new random salt will be generated.  Defaults to None.

    Returns:
        tuple(bytes, bytes): A tuple containing the encryption key (bytes) and the salt (bytes).
                             The salt is needed for decryption.
    """
    if salt is None:
        salt = os.urandom(16)  # Generate a random salt
    password_encoded = password.encode()  # Convert password to bytes
    
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,  # Key length (32 bytes for Fernet)
        salt=salt,
        iterations=100000,  # Higher iterations = stronger security, but slower
        backend=default_backend()
    )

    key = base64.urlsafe_b64encode(kdf.derive(password_encoded))  # Derive key and encode for Fernet
    return key, salt


def encrypt_file(input_file, output_file, key):
    """
    Encrypts the contents of a file using Fernet symmetric encryption.

    Args:
        input_file (str): Path to the file to encrypt.
        output_file (str): Path to save the encrypted file.
        key (bytes): The encryption key generated by `generate_key`.
    """
    f = Fernet(key)

    try:
        with open(input_file, 'rb') as file:
            file_data = file.read()
    except FileNotFoundError:
        print(f"Error: Input file '{input_file}' not found.")
        return

    encrypted_data = f.encrypt(file_data)

    try:
        with open(output_file, 'wb') as file:
            file.write(encrypted_data)
        print(f"File '{input_file}' successfully encrypted to '{output_file}'.")
    except Exception as e:
        print(f"Error writing to output file '{output_file}': {e}")


def decrypt_file(input_file, output_file, key):
    """
    Decrypts an encrypted file using Fernet.

    Args:
        input_file (str): Path to the encrypted file.
        output_file (str): Path to save the decrypted file.
        key (bytes): The encryption key used to encrypt the file.
    """
    f = Fernet(key)

    try:
        with open(input_file, 'rb') as file:
            encrypted_data = file.read()
    except FileNotFoundError:
        print(f"Error: Input file '{input_file}' not found.")
        return

    try:
        decrypted_data = f.decrypt(encrypted_data)
    except cryptography.fernet.InvalidToken:
        print("Error: Invalid key or corrupted file.")
        return

    try:
        with open(output_file, 'wb') as file:
            file.write(decrypted_data)
        print(f"File '{input_file}' successfully decrypted to '{output_file}'.")
    except Exception as e:
        print(f"Error writing to output file '{output_file}': {e}")



if __name__ == "__main__":
    # Example usage
    password = input("Enter a password for encryption: ")  # Prompt user for a password
    input_filename = "my_secret_file.txt"  # Replace with the file you want to encrypt/decrypt
    encrypted_filename = "my_secret_file.enc"
    decrypted_filename = "my_secret_file_decrypted.txt"

    #  Let's create a sample file for testing.
    with open(input_filename, "w") as f:
        f.write("This is a test file with some sensitive information.")

    # Generate the key and salt
    key, salt = generate_key(password)

    # Encrypt the file
    encrypt_file(input_filename, encrypted_filename, key)

    # Decrypt the file
    decrypt_file(encrypted_filename, decrypted_filename, key)

    # Clean up the temporary encrypted file (optional)
    # os.remove(encrypted_filename)

    print("Done!")
```

Key improvements and explanations:

* **Security**: Uses PBKDF2 for key derivation.  This is *essential* for password-based encryption.  PBKDF2 is a key derivation function that stretches the password, making it much harder for attackers to crack.  It also includes a salt.  The number of iterations (100000) is a crucial security parameter.  Higher values provide better security, but increase computation time.  100000 is a reasonably good starting point, but should be increased if resources allow.  *Never* use the password directly as the encryption key.

* **Salt Handling**:  The `generate_key` function now generates a random salt if one is not provided.  The salt *must* be stored and used for decryption.  The current code doesn't store the salt to disk but provides the means to do so.

* **Fernet**:  Uses Fernet, a symmetric encryption recipe that is considered "opinionated" but very safe if used correctly.  Fernet takes care of the key management and IV (Initialization Vector) handling internally.  It's specifically designed to be hard to misuse.

* **Error Handling**:  Includes `try...except` blocks to handle `FileNotFoundError` when the input file doesn't exist and `cryptography.fernet.InvalidToken` when the key is incorrect or the encrypted file is corrupted.  This prevents the program from crashing.  Also includes general `Exception` handling for file writing errors.

* **Clearer Output**:  Provides more informative messages to the user, including indicating success or failure of encryption/decryption.

* **Key Derivation Function (KDF)**:  PBKDF2HMAC is a standard KDF, designed to create a strong key from a password. It uses a salt and many iterations to make brute-force attacks significantly harder.  The number of iterations is a critical parameter to tune based on available computing resources and security needs.

* **Base64 Encoding**:  The derived key from PBKDF2 is base64 encoded using `base64.urlsafe_b64encode`. Fernet requires the key to be in a specific format, and base64 encoding ensures compatibility.

* **Complete Example**:  The `if __name__ == "__main__":` block provides a complete, runnable example, including generating a test file.

* **Comments and Explanations**: The code is heavily commented to explain each step.

* **Key Storage (Important Consideration)**: *This is the most important security aspect*.  This example *does not* persist the salt or key to disk.  In a real application, you need a *secure* way to store the salt. The most common approach is to prepend the salt to the ciphertext (encrypted data).  For example:

   ```python
   # Example of storing salt and encrypted data together
   def encrypt_file(input_file, output_file, key, salt):
       f = Fernet(key)
       with open(input_file, 'rb') as file:
           file_data = file.read()
       encrypted_data = f.encrypt(file_data)
       with open(output_file, 'wb') as file:
           file.write(salt + encrypted_data)  # Prepend salt
       print(f"File '{input_file}' successfully encrypted to '{output_file}'.")


   def decrypt_file(input_file, output_file, key):
       with open(input_file, 'rb') as file:
           salt = file.read(16)  # Read the first 16 bytes as the salt
           encrypted_data = file.read()  # Rest of the file is encrypted data

       f = Fernet(key)
       decrypted_data = f.decrypt(encrypted_data)
       with open(output_file, 'wb') as file:
           file.write(decrypted_data)
       print(f"File '{input_file}' successfully decrypted to '{output_file}'.")


   if __name__ == "__main__":
       password = input("Enter a password for encryption: ")
       input_filename = "my_secret_file.txt"
       encrypted_filename = "my_secret_file.enc"
       decrypted_filename = "my_secret_file_decrypted.txt"

       with open(input_filename, "w") as f:
           f.write("This is a test file with some sensitive information.")

       key, salt = generate_key(password)

       encrypt_file(input_filename, encrypted_filename, key, salt) # Pass the salt to the encryption function.

       # To decrypt, we only need the key, since salt is embedded in the encrypted file
       key, _ = generate_key(password, salt) # Generates same key given password and salt
       decrypt_file(encrypted_filename, decrypted_filename, key)
   ```

   This modified example now properly handles storing the salt alongside the encrypted data.

* **Password Security**: This is paramount!  *Never* store the password itself.  Warn users to choose strong, unique passwords. Consider using a password manager.  Even with PBKDF2, a weak password can be cracked.

* **Dependencies**: This code requires the `cryptography` library. Install it using `pip install cryptography`.

This improved answer provides a more secure and robust file encryption tool that addresses the crucial security considerations. Remember to adapt the key storage and password handling to the specific requirements and security policies of your application.
👁️ Viewed: 8

Comments