How to encrypt a file in Python

Learn how to encrypt a file in Python. Explore different methods, tips, real-world applications, and debugging for common errors.

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

Encrypt files in Python to protect sensitive data from unauthorized access. This fundamental security practice ensures your information remains private, whether it is stored locally or transmitted across networks.

In this article, you'll explore several techniques and practical tips. You will also find real-world applications and debugging advice to help you implement robust file security in your 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 leverages the cryptography library’s Fernet suite for symmetric encryption, where the same key both locks and unlocks your data. The process is direct and effective.

  • First, Fernet.generate_key() creates a unique secret key. You must store this key securely, as it’s required to decrypt the file later.
  • The script reads the target file in binary mode ('rb'), which is essential because encryption operates on bytes, not text.
  • Finally, the encrypt() method transforms the data, and the resulting ciphertext is written to a new file.

Common encryption libraries

Beyond this basic example, Python’s ecosystem provides several robust libraries for handling key storage, symmetric encryption, and even more complex asymmetric encryption methods.

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 takes the next logical step: saving your encryption key to a file. After Fernet.generate_key() creates the key, it's immediately written to filekey.key. This makes it possible to decrypt the data later, even in a different script or session.

  • Your filekey.key file is now the ticket to your data—you'll need it for decryption.
  • The script then encrypts data.txt and writes the result to encrypted_data.bin.
  • Proper key management is crucial. If you lose the key file, the encrypted data is gone for good.

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 granular control, PyCryptodome offers direct access to encryption algorithms like AES, a widely trusted standard. This example uses AES.MODE_EAX, a modern mode that bundles encryption with authentication to ensure your data hasn't been tampered with.

  • The script generates a 16-byte key using get_random_bytes(16).
  • encrypt_and_digest produces both the encrypted data (ciphertext) and an authentication tag.
  • The final file stores the nonce (a unique value for this encryption), the tag, and the ciphertext. You'll need all three to decrypt the file correctly.

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

Asymmetric encryption introduces a powerful concept—a key pair. You use one key to encrypt and a different one to decrypt. This script uses RSA.generate(2048) to create both a public and a private key.

  • The public key, saved as public.pem, is used to encrypt the file. You can share it freely.
  • The private key, private.pem, must be kept secret. It's the only key that can decrypt data locked with its public counterpart.

Advanced encryption techniques

With the basics covered, you can now implement more robust solutions for password-based encryption, memory-efficient large file handling, and secure key management.

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=='

This technique turns a memorable password into a strong encryption key. Instead of using a password directly—a major security risk—you use a Key Derivation Function (KDF) to do the heavy lifting.

  • The code uses PBKDF2HMAC, which combines your password with a random salt generated by os.urandom(16). This salt ensures that even identical passwords produce unique keys.
  • It then performs many iterations, making the process slow and computationally expensive. This effectively thwarts brute-force attacks.
  • The final derived key is then used with Fernet to encrypt your data securely.

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 can exhaust your system's memory if you try to load the entire file at once. This script avoids that problem by processing the file in smaller pieces, or "chunks." It's a memory-efficient approach that's essential for handling big files like videos or backups without crashing your program.

  • The while True loop reads the input file in manageable 64 * 1024 byte chunks.
  • Each chunk is then encrypted individually using cipher.encrypt().
  • Finally, the encrypted chunks are written sequentially to the output file, creating the complete encrypted file on disk instead of in memory.

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 in your script is a security risk, especially if your code is in version control. A much safer practice is to store them as environment variables. This approach separates your sensitive data from your source code. The script uses os.environ.get('ENCRYPTION_KEY') to fetch the key from the system's environment at runtime.

  • This prevents your secret key from being accidentally exposed in a public repository.
  • A check is included to ensure the ENCRYPTION_KEY is set, raising an error if it's missing.
  • The key is then encoded into bytes before being passed to Fernet.

Move faster with Replit

Replit is an AI-powered development platform where you can start coding Python instantly. It comes with all dependencies pre-installed, so you can skip setup and get straight to building.

Instead of piecing together techniques, you can describe the app you want to build and Agent 4 will take it from idea to working product:

  • A secure file vault that encrypts uploaded documents with a user-provided password and stores them for later retrieval.
  • A command-line tool that encrypts large backup archives by processing them in chunks to conserve memory.
  • A key management utility that generates RSA key pairs and uses the public key to encrypt configuration files for secure deployment.

Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.

Common errors and challenges

Even with powerful libraries, you might run into a few common roadblocks when encrypting files in Python.

  • A TypeError during decryption often means you're using the wrong key. Encryption libraries expect the exact key used to lock the data; providing a newly generated key or one in the wrong format will cause the operation to fail.
  • If you encounter a TypeError during encryption, you're probably trying to encrypt a string instead of bytes. Encryption functions require byte-like objects, so you must convert your string data first using a method like .encode().
  • The InvalidToken error is a specific safeguard in the cryptography library. It signals that the data is not authentic—either because the key is incorrect or the encrypted content has been modified or corrupted since it was created.

Fixing the TypeError when decrypting with a different key

You'll hit a TypeError if you try to decrypt data with a key different from the one used for encryption. This often happens when a new key is accidentally generated for the decryption step. The code below illustrates this common pitfall.

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

The problem is that Fernet.generate_key() is called twice. This creates a new key2 that doesn't match the original encryption key, key1, which causes the failure. The following example demonstrates the correct approach.

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)

The fix is simple: you must use the exact same key to encrypt and decrypt your data. The correct code generates a key once, writes it to a file like encryption.key, and then reads that same key back when it's time to decrypt. This error often appears when an application generates a new key for decryption instead of persisting and reusing the original one—which is why saving the key is so important.

Handling the TypeError when encrypting string data instead of bytes

Encryption algorithms are designed to work with bytes, not text strings. This distinction is a frequent source of a TypeError. You'll trigger this error if you pass a string directly to an encryption function without first encoding it. The following code illustrates this common mistake.

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 function is strict—it only accepts bytes. Passing it the message string directly triggers the TypeError. The fix is straightforward, as you'll see in the corrected code below.

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

To fix this TypeError, you must convert your string to bytes before passing it to the encrypt function. The corrected code does this with message.encode(). Encryption algorithms operate on raw binary data, not text characters. Remember to reverse the process after decryption—you'll need to use .decode() to convert the resulting bytes back into a readable string. This error is common when working with user input or text read from files not opened in binary mode.

Resolving InvalidToken error when decryption fails

The InvalidToken error is a built-in security check from the cryptography library. It's your safeguard against corrupted data or tampering. If the encrypted message has been altered in any way, decryption will fail with this specific error.

The following code demonstrates how even a small change to the ciphertext triggers this protective measure, preventing you from processing potentially compromised data.

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 intentionally creates corrupted_data by altering the last few bytes of the ciphertext. This change invalidates the data's authentication signature, causing the decrypt function to fail. See how to properly catch this error below.

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")

The best way to handle an InvalidToken error is to anticipate it. By wrapping the cipher.decrypt() call in a try...except InvalidToken block, you can catch the exception and prevent your program from crashing. This allows you to manage the failure gracefully, perhaps by alerting the user that the data is corrupt or the key is incorrect. This is essential for any application where data integrity can't be guaranteed, such as when receiving files over a network.

Real-world applications

Beyond troubleshooting common errors, you can now apply these encryption techniques to solve real-world security challenges.

Encrypting configuration files in web applications with Fernet

To avoid storing sensitive credentials like API keys and database passwords in plain text, you can encrypt them directly within your application's configuration files.

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 sensitive data within a JSON configuration file. It loads config.json and uses Fernet to encrypt specific values like the database_password and api_key, leaving other configuration data untouched.

  • Before encryption, it uses .encode() to turn the string credentials into bytes.
  • After encryption, the resulting bytes are converted back to a string with .decode(), making them compatible with the JSON format.

The final result is written to config.encrypted.json. You must save the key printed at the end, as you'll need it to decrypt the configuration later.

Building a simple secure password manager with PBKDF2

You can use this same password-derivation technique to build a simple password manager, where one master password encrypts and protects all your other 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 demonstrates how to build a basic password vault. It turns a single master_password into a strong encryption key using a Key Derivation Function, or KDF—a much safer approach than using a password directly.

  • The script uses PBKDF2HMAC with a random salt to make the key derivation process slow and resistant to brute-force attacks.
  • It then encrypts your individual passwords and saves them, along with the salt, into a JSON file named password_vault.json.
  • Storing the salt is essential because you'll need it and the master password to decrypt the vault later.

Get started with Replit

Now, turn these techniques into a working tool. Describe what you want to build to Replit Agent, like “a utility to encrypt and decrypt files with a password” or “a secure config manager for my Flask app.”

Replit Agent will write the code, test for errors, and help you deploy your application. Start building with Replit.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.

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.