AES isn’t just one thing; it’s a family of block ciphers with different modes of operation and key sizes, and picking the right combination is crucial for both security and performance.

Let’s see AES-GCM in action, encrypting and decrypting a short message.

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

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

# Generate a 12-byte nonce (Initialization Vector for GCM)
nonce = os.urandom(12)

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

# Authenticated Encryption with Associated Data (AEAD) using GCM mode
# Associated data is optional but authenticated alongside the plaintext
associated_data = b"This is authenticated but not encrypted."

# Create a cipher object
cipher_gcm = Cipher(algorithms.AES(key), modes.GCM(nonce, associated_data), backend=default_backend())

# Create an encryptor object
encryptor = cipher_gcm.encryptor()

# Encrypt the plaintext
ciphertext = encryptor.update(plaintext) + encryptor.finalize()

# Get the authentication tag
tag = encryptor.tag

print(f"Plaintext: {plaintext}")
print(f"Ciphertext: {ciphertext}")
print(f"Tag: {tag}")

# --- Decryption ---

# Create a cipher object for decryption
# Note: The nonce and associated_data MUST be the same as during encryption
decryptor_gcm = Cipher(algorithms.AES(key), modes.GCM(nonce, associated_data), backend=default_backend())

# Create a decryptor object
decryptor = decryptor_gcm.decryptor()

# Feed the ciphertext to the decryptor
decryptor.update(ciphertext)

# Finalize decryption and verify the tag
try:
    decrypted_plaintext = decryptor.finalize_with_tag(tag)
    print(f"Decrypted Plaintext: {decrypted_plaintext}")
except InvalidTag:
    print("Decryption failed: Authentication tag is invalid!")

AES (Advanced Encryption Standard) is a symmetric block cipher, meaning it uses the same key for both encryption and decryption. It operates on fixed-size blocks of data, typically 128 bits. The "AES" itself refers to the algorithm for transforming these blocks, and it can use key sizes of 128, 192, or 256 bits. The real magic, and the source of much confusion, lies in the modes of operation. Modes determine how AES is applied to data larger than a single block, and they have profound implications for security, performance, and how you handle initialization vectors (IVs) or nonces.

The core problem AES modes solve is turning a block cipher (which encrypts fixed-size chunks) into a stream cipher or a way to handle arbitrary-length messages securely. Without a mode, you’d have to encrypt each 128-bit block independently, which is insecure because identical plaintext blocks would produce identical ciphertext blocks, revealing patterns. Modes introduce randomness and complexity.

Here’s a breakdown of common modes:

  • ECB (Electronic Codebook): The simplest mode. Each 128-bit block of plaintext is encrypted independently with the same key. It’s never recommended for anything beyond encrypting a single block of random data because identical plaintext blocks produce identical ciphertext blocks. Think of it like a dictionary where each word always translates to the same coded word.
    • Why it’s bad: It preserves patterns. If you encrypt an image, you’ll still see the outline of the image in the ciphertext.
  • CBC (Cipher Block Chaining): Before encrypting a plaintext block, it’s XORed with the previous ciphertext block. For the very first block, a randomly generated Initialization Vector (IV) is used. This chaining ensures that identical plaintext blocks produce different ciphertext blocks.
    • Why it’s good: It hides patterns in the plaintext.
    • Why it’s tricky: Requires a unique, unpredictable IV for each encryption. The IV doesn’t need to be secret, but it must be unique. If you reuse an IV with the same key, it can lead to serious security vulnerabilities. Decryption requires padding if the plaintext isn’t a multiple of the block size.
  • CTR (Counter): Turns AES into a stream cipher. It encrypts a sequence of counter values (which are derived from a nonce and a counter) and XORs the result with the plaintext. Each block is encrypted independently, but the "key stream" generated by the counter is unique for each block.
    • Why it’s good: High performance (can be parallelized for encryption and decryption), no padding required, and allows random access to ciphertext blocks.
    • Why it’s tricky: Like CBC, the nonce must be unique for each encryption with the same key. Reusing a nonce/counter combination is catastrophic.
  • GCM (Galois/Counter Mode): An Authenticated Encryption with Associated Data (AEAD) mode. It combines CTR mode for encryption with a Galois Message Authentication Code (GMAC) for integrity and authenticity. This means it not only encrypts your data but also provides a cryptographic guarantee that the data hasn’t been tampered with and that it originated from the holder of the key. It also allows for "associated data" which is authenticated but not encrypted.
    • Why it’s great: Provides confidentiality, integrity, and authenticity in one go, often with good performance. It’s the modern standard for many applications like TLS.
    • Why it’s tricky: The nonce must be unique for each encryption with the same key. Reusing a nonce with GCM is a critical security failure that can break both confidentiality and authenticity. The tag generated by GCM must be transmitted along with the ciphertext and verified during decryption.
  • CCM (Counter with CBC-MAC): Another AEAD mode, but generally considered less performant than GCM. It combines CTR mode for encryption with CBC-MAC for authentication.

Key Sizes:

  • 128-bit: The fastest, and for most current applications, considered secure enough. Attacks against AES-128 are theoretical and require immense computational power.
  • 192-bit: Offers a slightly larger security margin but is not significantly more secure than 128-bit in practice for most threat models, and it’s slower.
  • 256-bit: Provides the highest theoretical security margin. It’s recommended for extremely long-term security needs or against adversaries with the potential for quantum computing (though quantum computers are still largely theoretical for breaking crypto). The performance difference between 128-bit and 256-bit is often negligible on modern hardware due to dedicated AES instructions.

When to Use Each:

  • ECB: Never, unless you are encrypting a single, random 128-bit block.
  • CBC: If you need compatibility with older systems or have specific reasons not to use AEAD modes. You must manage IVs carefully and handle padding correctly. It only provides confidentiality, not integrity.
  • CTR: When you need a stream cipher, high performance, or parallelizable encryption/decryption. Again, IV/nonce uniqueness is paramount, and it only provides confidentiality.
  • GCM: This is the go-to for most modern applications. Use it when you need confidentiality, integrity, and authenticity. It’s used in TLS 1.2+, SSH, and many other protocols. Ensure unique nonces.
  • CCM: An alternative AEAD mode, often used in constrained environments where GCM might not be supported.

The one thing most people don’t realize about AES modes is how devastating nonce reuse can be, especially in AEAD modes like GCM. In GCM, reusing a nonce with the same key not only allows an attacker to potentially forge messages (break authenticity) but can also reveal the authentication key, which in turn can be used to decrypt all messages encrypted with that key and nonce combination (breaking confidentiality). The nonce doesn’t need to be secret, but it absolutely, positively must be unique. A common strategy is to use a 12-byte (96-bit) nonce and increment a counter part of it for each message.

The next thing you’ll grapple with is managing keys securely, especially when dealing with multiple services or distributed systems.

Want structured learning?

Take the full Cryptography course →