AES encryption in Python is surprisingly straightforward, but the real trick is understanding that you’re likely just a thin layer away from a much more complex and secure system, and you’re responsible for the whole chain.

Let’s look at a basic example using the cryptography library, which is the modern, recommended way to handle cryptography in Python.

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
import os

# Generate a random 256-bit AES key
key = os.urandom(32) # 32 bytes = 256 bits

# Generate a random 128-bit Initialization Vector (IV)
iv = os.urandom(16) # 16 bytes = 128 bits

# Data to encrypt
plaintext = b"This is a secret message."

# Create an AES cipher object in CBC mode
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()).encryptor()

# PKCS7 padding is common for AES
padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_data = padder.update(plaintext) + padder.finalize()

# Encrypt the padded data
ciphertext = cipher.update(padded_data) + cipher.finalize()

print(f"Key: {key.hex()}")
print(f"IV: {iv.hex()}")
print(f"Ciphertext: {ciphertext.hex()}")

# --- Decryption ---
# You'll need the same key and IV for decryption

# Create a cipher object for decryption
decryptor = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()).decryptor()

# Decrypt the ciphertext
decrypted_padded_data = decryptor.update(ciphertext) + decryptor.finalize()

# Unpad the data
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
decrypted_plaintext = unpadder.update(decrypted_padded_data) + unpadder.finalize()

print(f"Decrypted Plaintext: {decrypted_plaintext.decode()}")

This code demonstrates encrypting and then decrypting a simple message. We generate a random key and an Initialization Vector (IV), which are crucial for security. The data is then padded using PKCS7 to ensure it’s a multiple of the AES block size (16 bytes). Finally, the encryption and decryption operations are performed using the cryptography library.

The mental model for AES encryption involves a few key components:

  1. The Key: This is the secret. It’s a sequence of bytes that determines the specific transformation applied to the data. For AES, keys can be 128, 192, or 256 bits long. Longer keys are generally more secure but can be slightly slower. The os.urandom(32) call generates a cryptographically secure random 256-bit key.

  2. The Algorithm: This is the mathematical process that scrambles the data. In our case, it’s AES.

  3. The Mode of Operation: This dictates how the algorithm is applied to blocks of data. Common modes include Electronic Codebook (ECB), Cipher Block Chaining (CBC), Counter (CTR), and Galois/Counter Mode (GCM).

    • ECB is the simplest but least secure, as identical plaintext blocks result in identical ciphertext blocks, revealing patterns.
    • CBC is much better. Each block of plaintext is XORed with the previous ciphertext block before being encrypted. This means identical plaintext blocks will produce different ciphertext blocks. It requires an Initialization Vector (IV).
    • CTR turns a block cipher into a stream cipher, which can be parallelized and doesn’t require padding.
    • GCM is an authenticated encryption mode, meaning it provides both confidentiality (encryption) and integrity (ensures data hasn’t been tampered with). It’s generally the preferred mode for new applications.
  4. The Initialization Vector (IV): For modes like CBC, the IV is a random or pseudo-random number that is used to initialize the encryption process. It doesn’t need to be secret, but it must be unique for each encryption performed with the same key. Reusing an IV with the same key in CBC mode can severely compromise security. os.urandom(16) generates a unique IV for each encryption.

  5. Padding: AES is a block cipher, meaning it operates on fixed-size blocks of data (16 bytes for AES). If your data isn’t an exact multiple of the block size, you need to add extra bytes (padding) to make it fit. PKCS7 is a standard padding scheme that adds N bytes, each with the value N, to fill the last block. The decryptor then removes this padding.

The cryptography library handles the low-level details of the AES algorithm and the chosen mode. Your primary responsibilities are:

  • Key Management: How do you generate, store, and retrieve your keys securely? This is often the hardest part of applied cryptography.
  • IV Generation: Ensure you generate a unique, unpredictable IV for every encryption operation when using modes like CBC or GCM.
  • Mode Selection: Choose a mode appropriate for your security needs. For new applications, GCM is often recommended due to its built-in authentication.
  • Data Integrity: If you’re not using an authenticated encryption mode like GCM, you’ll need a separate mechanism (like a Message Authentication Code, or MAC) to ensure the data hasn’t been altered.

A crucial, often overlooked aspect of using AES in CBC mode is the interaction between padding and decryption. If an attacker can control or observe the padding error messages during decryption, they might be able to mount a "padding oracle attack," where they repeatedly send slightly modified ciphertexts and observe whether decryption succeeds or fails due to bad padding, thereby inferring information about the plaintext. This is why authenticated encryption modes like GCM are so valuable, as they combine encryption and integrity checks.

The next step in your AES journey would be exploring authenticated encryption modes like GCM to ensure both confidentiality and integrity.

Want structured learning?

Take the full Cryptography course →