Encrypt and Decrypt Files in Python Using AES, Base64, and OpenSSL-Compatible Format

File encryption is an important technique used to protect sensitive information. Whether you are storing private documents, transferring files, or building a small security tool, encryption helps make sure that unauthorized users cannot easily read the original content.

In this article, we will explain in simple terms how file encryption works in Python using AES encryption, a password-based key, Base64 encoding, and an OpenSSL-compatible format. We will also cover common decryption problems such as Incorrect padding, Padding is incorrect, and the Windows error 'openssl' is not recognized.

This guide is written for beginners and explains the concepts step by step.


What Is File Encryption?

File encryption means converting normal readable data into unreadable data. The original file content is called plaintext. After encryption, the result is called ciphertext.

For example, a normal file may contain readable text, an image, a PDF, or any other data. After encryption, the file becomes a block of random-looking data. To convert it back to the original file, you need the correct password or key.

Encryption is useful for:

  • protecting private files;
  • securing backups;
  • sending files safely;
  • preventing unauthorized access;
  • storing confidential data.

What Is AES Encryption?

AES stands for Advanced Encryption Standard. It is one of the most common encryption algorithms used today.

In many Python scripts, AES is used through libraries such as pycryptodome. A common mode is AES-CBC, which means Advanced Encryption Standard in Cipher Block Chaining mode.

A typical AES-CBC encryption setup needs:

  • a secret key;
  • an initialization vector, also called IV;
  • the original file content;
  • padding, because AES works with fixed-size blocks.

AES uses blocks of 16 bytes. If the file content does not perfectly fit into 16-byte blocks, padding is added before encryption.


Example Python Encryption Logic

A typical encryption function may look like this:

def encrypt_file(file_path: str, passphrase: str) -> str:
    with open(file_path, 'rb') as f:
        content = f.read()

    salt = secrets.token_bytes(8)
    key, iv = evp_bytes_to_key(passphrase, salt, 32, 16)

    cipher = AES.new(key, AES.MODE_CBC, iv)
    encrypted = cipher.encrypt(pad(content, AES.block_size))

    result = b'Salted__' + salt + encrypted
    return base64.b64encode(result).decode('utf-8')

This function reads a file, encrypts it, and returns a Base64 string.

The output is not the original encrypted bytes directly. Instead, it is encoded in Base64 so it can be stored or copied as text.


What Does Salted__ Mean?

The encrypted result starts with:

Salted__

This is a special OpenSSL-compatible header. It tells the decrypting program that the encrypted data uses a salt.

The final encrypted structure is:

Salted__ + salt + encrypted_content

Then this full binary result is converted to Base64.

That is why the Base64 output often starts with:

U2FsdGVkX1

This is the Base64 version of Salted__.

If an encrypted string starts with U2FsdGVkX1, it is a strong sign that it uses the OpenSSL salted format.


What Is a Salt?

A salt is a random value added during encryption. It makes encryption safer because the same password will not always generate the same encrypted output.

For example, if you encrypt the same file twice with the same password, the result should still be different because the salt is different each time.

In this case, the salt is 8 bytes:

salt = secrets.token_bytes(8)

The salt is not secret. It is stored together with the encrypted data after the Salted__ header.


What Is evp_bytes_to_key?

The function evp_bytes_to_key derives the AES key and IV from the password and salt.

Example:

def evp_bytes_to_key(password: str, salt: bytes, key_len: int, iv_len: int):
    password_bytes = password.encode('utf-8')
    target_len = key_len + iv_len
    derived = b''
    block = b''

    while len(derived) < target_len:
        block = hashlib.md5(block + password_bytes + salt).digest()
        derived += block

    return derived[:key_len], derived[key_len:key_len + iv_len]

This function is compatible with the older OpenSSL EVP_BytesToKey method using MD5.

For AES-256-CBC, the script needs:

32 bytes for the key
16 bytes for the IV

That means the script derives a total of 48 bytes.


Is This Compatible With OpenSSL?

Yes, this format is compatible with the classic OpenSSL encryption format.

The matching OpenSSL settings are:

Algorithm: AES-256-CBC
Digest: MD5
Format: Salted OpenSSL format
Input/output: Base64
Padding: PKCS7
KDF: EVP_BytesToKey

The OpenSSL command for decryption would usually be:

openssl enc -aes-256-cbc -d -a -md md5 -in encrypted.txt -out decrypted_file -pass pass:your_password

The important part is:

-md md5

This is required because the Python key derivation function uses MD5.

You should not use:

-pbkdf2

unless the encryption script also used PBKDF2. In this case, it does not.


What If OpenSSL Is Not Recognized on Windows?

On Windows, you may see this error:

'openssl' is not recognized as an internal or external command,
operable program or batch file.

This means OpenSSL is not installed or it is not added to the Windows PATH.

There are several solutions.

You can install OpenSSL for Windows, use Git Bash if Git is installed, or run OpenSSL directly from its full installation path.

For example:

"C:\Program Files\OpenSSL-Win64\bin\openssl.exe" enc -aes-256-cbc -d -a -md md5 -in encrypted.txt -out decrypted_file

If the password contains special characters, it is safer to let OpenSSL ask for the password interactively instead of writing it directly in the command.


How to Decrypt the File in Python

A matching Python decrypt function should read the Base64 content, decode it, check for the Salted__ header, extract the salt, derive the key and IV, decrypt the content, and remove padding.

Example:

import base64
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad


def evp_bytes_to_key(password: str, salt: bytes, key_len: int, iv_len: int):
    password_bytes = password.encode("utf-8")
    target_len = key_len + iv_len
    derived = b""
    block = b""

    while len(derived) < target_len:
        block = hashlib.md5(block + password_bytes + salt).digest()
        derived += block

    return derived[:key_len], derived[key_len:key_len + iv_len]


def decrypt_file_content(input_file_path: str, passphrase: str, output_file_path: str):
    with open(input_file_path, "r", encoding="utf-8") as f:
        encrypted_b64 = f.read().strip()

    encrypted_b64 = encrypted_b64.replace("\n", "").replace("\r", "").strip()

    raw = base64.b64decode(encrypted_b64)

    if raw[:8] != b"Salted__":
        raise ValueError("Invalid encrypted format. Missing Salted__ header.")

    salt = raw[8:16]
    encrypted = raw[16:]

    if len(encrypted) % AES.block_size != 0:
        raise ValueError("Invalid encrypted data length.")

    key, iv = evp_bytes_to_key(passphrase, salt, 32, 16)

    cipher = AES.new(key, AES.MODE_CBC, iv)
    decrypted = unpad(cipher.decrypt(encrypted), AES.block_size)

    with open(output_file_path, "wb") as f:
        f.write(decrypted)

This function should work with the encryption function shown earlier, as long as the password and encrypted data are correct.


Common Error: Incorrect padding

One common error is:

binascii.Error: Incorrect padding

This usually happens when the script tries to decode something that is not valid Base64.

Common causes include:

  • passing the file path instead of the file content;
  • copying only part of the Base64 string;
  • missing characters at the end;
  • storing the encrypted value inside JSON and decoding the full JSON instead of only the encrypted field;
  • extra text before or after the Base64 value.

For example, this is wrong:

decrypt_file_content("encrypted.txt", passkey, output)

if the function expects the actual Base64 string, not a file path.

The correct approach is to open the file first and read its content.


Common Error: Padding is incorrect

Another common error is:

ValueError: Padding is incorrect.

This means the Base64 was decoded and AES decryption was attempted, but the decrypted result was not valid.

The most common reason is a wrong password.

Other possible causes include:

  • the encrypted content is corrupted;
  • the Base64 string was changed;
  • the wrong file is being decrypted;
  • the encryption and decryption functions do not use the same key derivation method;
  • the wrong AES mode is being used;
  • the password has hidden spaces or newline characters.

For example, these are all different passwords:

password
password 
Password
"password"

Even one extra space changes the key completely.

A useful debug check is:

print(repr(passphrase))
print(len(passphrase))

This helps detect hidden spaces, quotation marks, or newline characters.


How to Check If the Encrypted File Looks Correct

A valid encrypted Base64 string from this format usually starts with:

U2FsdGVkX1

This means the decoded data starts with:

Salted__

You can check it in Python:

print(encrypted_b64[:20])

Example output:

U2FsdGVkX19ziBFf3vPb

This is a good sign. It means the encrypted content is probably in the expected OpenSSL salted format.

However, this does not guarantee that the password is correct. A wrong password can still produce the Padding is incorrect error.


Best Practices for File Encryption Scripts

When building a file encryption and decryption script, it is important to follow some basic safety rules.

First, never upload sensitive encrypted files and passwords to random online decryption tools. An online tool receives both the encrypted data and the password, so it can decrypt and store the original content.

Second, always test encryption and decryption together using a small test file. This confirms that both functions are compatible.

Third, keep the encrypted Base64 output exactly as generated. Do not manually edit it.

Fourth, be careful with passwords. Spaces, uppercase letters, lowercase letters, special characters, and encoding all matter.

Fifth, for new projects, consider using a modern password-based key derivation function such as PBKDF2, scrypt, or Argon2 instead of the legacy MD5-based EVP_BytesToKey method.


Is MD5-Based EVP_BytesToKey Still Recommended?

The MD5-based EVP_BytesToKey method is mostly used for compatibility with older OpenSSL encryption commands.

It can be useful when you need to decrypt existing data that was already encrypted this way.

However, for new encryption systems, it is better to use a stronger key derivation method such as:

PBKDF2
scrypt
Argon2

These methods are designed to make password guessing harder.

Still, if the goal is compatibility with an existing OpenSSL-style encrypted format, then the MD5-based method may be necessary.


Simple Troubleshooting Checklist

If decryption fails, check the following:

Does the encrypted Base64 start with U2FsdGVkX1?
Is the password exactly the same?
Are there hidden spaces in the password?
Was the full Base64 string copied?
Is the encrypted content read from the correct file?
Is the script using AES-256-CBC?
Is the script using MD5 in evp_bytes_to_key?
Is PBKDF2 disabled?
Is the encrypted data length a multiple of 16 after removing the Salted__ header and salt?

If the Base64 starts correctly but Padding is incorrect appears, the most likely issue is the password or corrupted encrypted data.


Conclusion

Python can encrypt files in a format that is compatible with OpenSSL by using AES-256-CBC, a salt, MD5-based EVP_BytesToKey, PKCS7 padding, and Base64 encoding.

The important format is:

Base64(Salted__ + salt + encrypted_data)

When decrypting, the same password, salt, key derivation method, AES mode, and padding must be used.

Errors such as Incorrect padding usually point to invalid Base64 input, while Padding is incorrect usually means the wrong password or corrupted encrypted data.

For safe use, decryption should be done locally instead of using online tools, especially when working with private or sensitive files.

Meta description:
Beginner-friendly guide explaining Python AES file encryption and decryption using Base64, OpenSSL-compatible Salted__ format, EVP_BytesToKey, MD5, and common decrypt errors.

SEO keywords/tags:
Python file encryption, AES-256-CBC Python, decrypt AES Python, OpenSSL compatible encryption, Salted__ format, EVP_BytesToKey Python, MD5 key derivation, Base64 decrypt error, Incorrect padding Python, Padding is incorrect AES, PyCryptodome AES, Python encryption tutorial, OpenSSL decrypt Windows, AES CBC padding error, encrypted file decrypt guide

This article is inspired by real-world challenges we tackle in our projects. If you're looking for expert solutions or need a team to bring your idea to life,

Let's talk!

    Please fill your details, and we will contact you back

      Please fill your details, and we will contact you back