How to encrypt a file in Python

Learn to encrypt files in Python. Explore different methods, tips, real-world applications, and how to debug common errors.

How to encrypt a file in Python
Published on: 
Tue
Mar 3, 2026
Updated on: 
Wed
Mar 4, 2026
The Replit Team Logo Image
The Replit Team

File encryption in Python is crucial to protect sensitive data from unauthorized access. Python's cryptography libraries offer robust tools to secure information and ensure privacy and integrity for your applications.

Here, you'll explore various encryption techniques with practical tips. You'll discover real world applications and receive debugging advice to help you implement secure file operations in your own projects.

Basic encryption with the cryptography library

from cryptography.fernet import Fernet
key = Fernet.generate_key()
f = Fernet(key)
with open('secret.txt', 'rb') as file:
file_data = file.read()
encrypted_data = f.encrypt(file_data)
with open('secret.encrypted', 'wb') as file:
file.write(encrypted_data)--OUTPUT--No visible output, but creates 'secret.encrypted' file

This snippet uses the Fernet class for symmetric encryption, a system where the same key both encrypts and decrypts data. The process is quite direct:

  • A secure key is created with Fernet.generate_key(). You must store this key somewhere safe—it’s the only way to access the encrypted file later.
  • The file is opened in binary read mode ('rb') because encryption algorithms operate on bytes, not text strings.
  • The encrypt() method ciphers the file's contents, which are then written to a new .encrypted file in binary write mode ('wb').

Common encryption libraries

While the basic Fernet example is a solid start, Python's ecosystem offers more advanced options for key storage and alternative encryption algorithms.

Using the Fernet symmetric encryption with key storage

from cryptography.fernet import Fernet
key = Fernet.generate_key()
with open('filekey.key', 'wb') as filekey:
filekey.write(key)
cipher = Fernet(key)
with open('data.txt', 'rb') as file:
encrypted_data = cipher.encrypt(file.read())
with open('encrypted_data.bin', 'wb') as file:
file.write(encrypted_data)--OUTPUT--No visible output, but creates 'encrypted_data.bin' and 'filekey.key' files

This example builds on the previous one by showing you how to persist the encryption key. Since you'll need the exact same key for decryption, saving it is a critical step.

  • The script generates a key and immediately writes it to a separate file, filekey.key, using binary write mode ('wb').
  • With the key saved, it then encrypts the target file, data.txt.

Your main responsibility now becomes managing this key file securely—if it's lost, the data is irrecoverable.

Using AES encryption with PyCryptodome

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
key = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_EAX)
with open('document.txt', 'rb') as file:
data = file.read()
ciphertext, tag = cipher.encrypt_and_digest(data)
with open('encrypted.bin', 'wb') as file:
[file.write(x) for x in (cipher.nonce, tag, ciphertext)]--OUTPUT--No visible output, but creates 'encrypted.bin' file containing nonce, tag, and ciphertext

For more direct control, you can use PyCryptodome to implement AES encryption. This example generates a 16-byte key with get_random_bytes and initializes the cipher using AES.MODE_EAX. This mode is important because it doesn't just encrypt your data; it also helps verify its integrity.

  • The encrypt_and_digest() method returns both the encrypted content (ciphertext) and an authentication tag.
  • To decrypt the file later, you'll need three components stored in the output file: the nonce (a unique value for this encryption), the tag, and the ciphertext itself.

Using RSA asymmetric encryption for file security

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
key = RSA.generate(2048)
with open('private.pem', 'wb') as f:
f.write(key.export_key('PEM'))
with open('public.pem', 'wb') as f:
f.write(key.publickey().export_key('PEM'))
cipher = PKCS1_OAEP.new(key.publickey())
with open('message.txt', 'rb') as file:
encrypted_data = cipher.encrypt(file.read())
with open('encrypted_message.bin', 'wb') as file:
file.write(encrypted_data)--OUTPUT--No visible output, but creates 'private.pem', 'public.pem', and 'encrypted_message.bin' files

This example introduces asymmetric encryption with RSA. Unlike symmetric methods, it's a system that uses two keys: a public key to encrypt and a private key to decrypt. The code generates this pair with RSA.generate(2048).

  • Your public key, saved to public.pem, can be shared with anyone who needs to send you encrypted files.
  • Your private key, saved to private.pem, must be kept secret—it's the only key that can decrypt data encrypted with its public counterpart.
  • The PKCS1_OAEP cipher uses the public key to encrypt the file, ensuring its confidentiality.

Advanced encryption techniques

With these fundamentals in place, you can now tackle more complex scenarios like deriving keys from passwords, encrypting large files, and managing keys securely.

Password-based encryption with key derivation function

import os
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.fernet import Fernet
import base64
password = b"my_secure_password"
salt = os.urandom(16)
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000)
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)
encrypted = f.encrypt(b"Secret data")
print(f"Encrypted: {encrypted}")--OUTPUT--Encrypted: b'gAAAAABjw...[truncated]...XQMg=='

Instead of using a password directly for encryption, this method securely derives a key from it using a Key Derivation Function (KDF). This approach is much safer because it adds layers of protection against common attacks. You'll need to save the salt alongside the encrypted data to decrypt it later.

  • The PBKDF2HMAC function combines your password with a random salt, which prevents attackers from using precomputed hash tables.
  • Setting a high number of iterations makes the process computationally intensive, slowing down brute force attempts.
  • The final derived key is then used with Fernet to perform the encryption.

Encrypting large files with chunking for memory efficiency

from cryptography.fernet import Fernet
key = Fernet.generate_key()
cipher = Fernet(key)
chunk_size = 64 * 1024
with open('large_file.mp4', 'rb') as infile, open('encrypted_file.enc', 'wb') as outfile:
while True:
chunk = infile.read(chunk_size)
if not chunk:
break
encrypted_chunk = cipher.encrypt(chunk)
outfile.write(encrypted_chunk)--OUTPUT--No visible output, but creates 'encrypted_file.enc'

Encrypting large files requires a different approach to avoid memory overload. Instead of reading the entire file at once, this script processes it in manageable pieces. It's a simple yet effective strategy for handling big data without crashing your system.

  • The code reads the input file in fixed-size segments, defined by chunk_size.
  • Each chunk is encrypted individually and then immediately written to the output file.
  • This loop continues until infile.read(chunk_size) returns an empty chunk, ensuring the entire file is processed efficiently.

Using environment variables for secure key management

import os
from cryptography.fernet import Fernet
key = os.environ.get('ENCRYPTION_KEY')
if not key:
raise ValueError("No encryption key found in environment variables")
cipher = Fernet(key.encode())
encrypted_data = cipher.encrypt(b"Top secret information")
print(f"Encrypted with env key: {encrypted_data[:20]}...")--OUTPUT--Encrypted with env key: b'gAAAAABjw0Gdh7P...'

Hardcoding keys is risky. A much safer practice is to store them as environment variables, which keeps sensitive credentials separate from your source code. This method is ideal for deployed applications where you can configure secrets outside the codebase.

  • The script uses os.environ.get('ENCRYPTION_KEY') to fetch the key from the environment.
  • It includes a crucial check that raises a ValueError if the key isn't set, preventing the app from running insecurely.
  • Since environment variables are strings, the key is converted to bytes using .encode() before being passed to Fernet.

Move faster with Replit

Replit is an AI-powered development platform that transforms natural language into working applications. You can describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.

For the encryption techniques covered in this article, Replit Agent can turn them into production-ready tools:

  • Build a secure file locker that uses a password to encrypt and decrypt personal documents.
  • Create a secure sharing utility where you can encrypt files with a recipient's public key.
  • Deploy a tool for encrypting large data backups or media files efficiently without memory issues.

Describe your app idea, and Replit Agent writes the code, tests it, and fixes issues automatically, all in your browser. Try Replit Agent to bring your own secure applications to life.

Common errors and challenges

Even with the right tools, you might run into a few common errors, but they're usually straightforward to fix.

  • Fixing a TypeError during decryption: This error often points to a key that's in the wrong format. For instance, passing a regular string as a key instead of a byte string will trigger a TypeError. While using a completely different key usually raises an InvalidToken error, an incorrectly formatted key is a common culprit for this type mismatch.
  • Handling a TypeError during encryption: Encryption libraries operate on binary data, not text. You'll hit this error if you try to encrypt a string directly or read a file in text mode instead of binary mode ('rb'). The solution is to always encode your string data into bytes (e.g., using .encode()) before passing it to an encryption function.
  • Resolving an InvalidToken error: This error is a security feature, not a simple bug. It means the data could not be authenticated during decryption, which typically happens for two reasons: you're using the wrong key, or the encrypted data has been corrupted or tampered with.

Fixing the TypeError when decrypting with a different key

A TypeError during decryption often points to a key mismatch. This happens if you don't save your original key and instead generate a new one with Fernet.generate_key() when it's time to decrypt. The following code shows this bug in action.

from cryptography.fernet import Fernet

# Generate a new key each time (bug)
key1 = Fernet.generate_key()
cipher1 = Fernet(key1)
encrypted_data = cipher1.encrypt(b"Secret message")

# Later trying to decrypt with a different key
key2 = Fernet.generate_key()
cipher2 = Fernet(key2)
decrypted_data = cipher2.decrypt(encrypted_data) # Will raise error

This code triggers an error because key2 is a completely new key, distinct from key1. Decryption is impossible without the original key. The following example shows how to manage the key properly for successful decryption.

from cryptography.fernet import Fernet

# Generate key and save it for later use
key = Fernet.generate_key()
cipher = Fernet(key)
encrypted_data = cipher.encrypt(b"Secret message")

# Store key for later decryption
with open('encryption.key', 'wb') as key_file:
key_file.write(key)

# Later, use the same key for decryption
with open('encryption.key', 'rb') as key_file:
stored_key = key_file.read()
cipher = Fernet(stored_key)
decrypted_data = cipher.decrypt(encrypted_data)

To avoid this error, you must use the exact same key for decryption that you used for encryption. The solution is to persist the key.

  • The code first saves the key generated by Fernet.generate_key() to a file.
  • Later, it reads that same key back before calling the decrypt() method.

This simple practice guarantees you're always using the correct key, preventing decryption failures. Remember to keep your key file secure.

Handling the TypeError when encrypting string data instead of bytes

Encryption libraries work with binary data, not text. If you try to encrypt a regular string, you'll get a TypeError because the library expects bytes. It's a common mistake when you're new to encryption. The following code shows this error.

from cryptography.fernet import Fernet

key = Fernet.generate_key()
cipher = Fernet(key)
message = "This is my secret message"
encrypted = cipher.encrypt(message) # TypeError: Data must be bytes

The encrypt() method receives a string variable, message, when it's designed to only process bytes. This mismatch triggers the TypeError. The corrected version below shows how to properly prepare your data for encryption.

from cryptography.fernet import Fernet

key = Fernet.generate_key()
cipher = Fernet(key)
message = "This is my secret message"
encrypted = cipher.encrypt(message.encode()) # Convert string to bytes
decrypted = cipher.decrypt(encrypted).decode() # Convert bytes back to string

The fix is straightforward: you must convert your string to bytes before encryption. The encrypt() method only operates on binary data, so calling message.encode() correctly prepares your string and prevents the TypeError.

  • After decryption, the data is still in bytes. You'll need to call .decode() to convert it back into a human-readable string.
  • This error often appears when you're working with user input or data read from text files, which Python handles as strings by default.

Resolving InvalidToken error when decryption fails

The InvalidToken error is a security alert, not a simple bug. It signals that the encrypted data failed its integrity check during decryption. This usually means you're using the wrong key or the data has been tampered with.

The following code demonstrates how corrupted data triggers this error, protecting you from potentially malicious content.

from cryptography.fernet import Fernet

key = Fernet.generate_key()
cipher = Fernet(key)
encrypted_data = cipher.encrypt(b"Secret message")

# Manually modify the encrypted data (simulating corruption)
corrupted_data = encrypted_data[:-5] + b'xxxxx'
decrypted_data = cipher.decrypt(corrupted_data) # Will raise InvalidToken

The code simulates data corruption by replacing the end of the encrypted_data with random bytes. When decrypt() is called, the data's signature no longer matches, triggering the error. The following example shows how to handle this correctly.

from cryptography.fernet import Fernet
from cryptography.fernet import InvalidToken

key = Fernet.generate_key()
cipher = Fernet(key)
encrypted_data = cipher.encrypt(b"Secret message")

# Manually modify the encrypted data (simulating corruption)
corrupted_data = encrypted_data[:-5] + b'xxxxx'

try:
decrypted_data = cipher.decrypt(corrupted_data)
except InvalidToken:
print("Decryption failed: data may be corrupted or wrong key used")

To handle this error gracefully, you should wrap the decrypt() call in a try...except InvalidToken block. This approach prevents your application from crashing and allows you to manage the failure cleanly.

  • It’s a best practice for any application where data integrity can't be guaranteed—for example, when receiving encrypted files over a network. By catching the exception, you can alert the user that the data is untrustworthy without halting the program.

Real-world applications

Moving beyond troubleshooting, you can now use these encryption methods to secure configuration files or build a simple password manager.

Encrypting configuration files in web applications with Fernet

You can use Fernet to encrypt sensitive data like an api_key or database_password directly within your configuration files, preventing them from being exposed in plain text.

from cryptography.fernet import Fernet
import json

# Load configuration with sensitive data
with open('config.json', 'r') as file:
config = json.load(file)

# Generate a key for encryption
key = Fernet.generate_key()
cipher = Fernet(key)

# Encrypt sensitive values
config['database_password'] = cipher.encrypt(config['database_password'].encode()).decode()
config['api_key'] = cipher.encrypt(config['api_key'].encode()).decode()

# Save encrypted configuration
with open('config.encrypted.json', 'w') as file:
json.dump(config, file)

print(f"Configuration encrypted. Key: {key.decode()}")

This script secures a configuration file by selectively encrypting its sensitive fields. It reads a standard config.json, then generates a unique encryption key using Fernet.

  • It targets specific values like database_password and encrypts them individually.
  • The encrypted bytes are decoded back into strings so they can be saved in a new JSON file, config.encrypted.json.
  • The script prints the key, which you must save to decrypt the configuration later.

Building a simple secure password manager with PBKDF2

You can use PBKDF2 to create a simple password vault where a single master password protects all your other stored credentials.

import base64, json, os
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.fernet import Fernet

# Generate a secure key from the master password
master_password = "SuperSecretMasterPassword"
salt = os.urandom(16)
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=100000)
key = base64.urlsafe_b64encode(kdf.derive(master_password.encode()))
cipher = Fernet(key)

# Encrypt and store passwords
vault = {"salt": base64.b64encode(salt).decode(), "passwords": {}}
vault["passwords"]["email"] = cipher.encrypt(b"email_password123").decode()
vault["passwords"]["banking"] = cipher.encrypt(b"banking_password456").decode()

# Save the vault
with open("password_vault.json", "w") as f:
json.dump(vault, f)

print(f"Password vault created with {len(vault['passwords'])} entries")

This script creates a secure vault by deriving a strong encryption key from a master_password. It's a robust approach because it combines the password with a random salt using PBKDF2HMAC, making it resistant to common attacks.

  • The derived key is used with Fernet to encrypt each credential individually.
  • The salt is stored alongside the encrypted data in a JSON file, as it’s essential for re-deriving the key to decrypt the vault later.

Get started with Replit

Turn what you've learned into a real tool. Just tell Replit Agent what to build, like "a file locker that uses a password" or "a utility to encrypt API keys in a config file."

The agent writes the code, tests for errors, and deploys your app from a single prompt. It's the fastest way to bring your secure tools to life. Start building with Replit.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

Get started for free

Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.