AES-GCM is actually a mode of operation for AES, not a separate encryption algorithm.
Here’s how AES-GCM, AES-CBC, and AES-CTR stack up in practice:
AES-GCM: The Modern All-Rounder
AES-GCM is the current gold standard for symmetric encryption, especially in network protocols like TLS. It’s an Authenticated Encryption with Associated Data (AEAD) mode, meaning it provides both confidentiality (encryption) and integrity/authenticity (ensuring data hasn’t been tampered with) in a single, efficient pass.
Why it’s great:
- Efficiency: It can be hardware-accelerated, making it very fast.
- Security: It’s robust against many common attacks, especially when implemented correctly.
- Simplicity: Provides both encryption and authentication, reducing the need for separate MAC (Message Authentication Code) calculations.
Example: Imagine you’re sending a secret message to a server. With AES-GCM, you encrypt the message, and it comes with a tag. The server decrypts the message and verifies the tag. If the tag doesn’t match, the message was either altered in transit or isn’t from the expected sender.
Under the hood: AES-GCM uses a combination of AES in Counter (CTR) mode for encryption and a Galois/Field multiplication for authentication. The "Associated Data" part means you can authenticate additional, unencrypted data alongside the ciphertext (e.g., headers).
Configuration:
When you implement AES-GCM, you’ll typically need:
- A Secret Key: This is your AES key (128, 192, or 256 bits).
- An Initialization Vector (IV) / Nonce: This must be unique for every encryption operation with the same key. A common length is 12 bytes (96 bits). Reusing an IV/nonce with the same key is catastrophic for GCM’s security.
- Associated Data (Optional): Any unencrypted metadata you want to authenticate.
Example in Python (using cryptography library):
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os
# Generate a random key (256-bit)
key = os.urandom(32)
# Generate a random nonce (12-byte)
nonce = os.urandom(12)
# Data to encrypt
plaintext = b"This is a secret message."
# Associated data (e.g., headers)
associated_data = b"header_info"
# Create cipher object
cipher = Cipher(algorithms.AES(key), modes.GCM(nonce), backend=default_backend())
encryptor = cipher.encryptor()
# Set associated data
encryptor.authenticate_additional_data(associated_data)
# Encrypt
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
# The encryptor object now also holds the authentication tag
tag = encryptor.tag
print(f"Ciphertext: {ciphertext.hex()}")
print(f"Tag: {tag.hex()}")
# --- Decryption ---
# Recreate cipher object with the same key and nonce
decryptor_cipher = Cipher(algorithms.AES(key), modes.GCM(nonce), backend=default_backend())
decryptor = decryptor_cipher.decryptor()
# Set associated data for verification
decryptor.authenticate_additional_data(associated_data)
# Provide the ciphertext and tag to the decryptor
try:
decrypted_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
print(f"Decrypted plaintext: {decrypted_plaintext.decode()}")
except Exception as e: # In real code, catch specific exceptions like InvalidTag
print(f"Decryption failed: {e}")
AES-CBC: The Older, But Still Used, Mode
AES-CBC (Cipher Block Chaining) was widely used for a long time. It’s a secure mode if implemented correctly with proper padding and a separate MAC for integrity.
How it works: Each block of plaintext is XORed with the previous ciphertext block before being encrypted. For the very first block, it’s XORed with an Initialization Vector (IV).
Key characteristics:
- Confidentiality only: CBC only provides encryption. You must use a separate MAC (like HMAC-SHA256) to ensure data integrity and authenticity. Without a MAC, CBC is vulnerable to padding oracle attacks.
- IV requirement: Like GCM, CBC requires an IV. The IV does not need to be secret, but it must be unpredictable and unique for each message encrypted with the same key. Reusing an IV with the same key is a serious security flaw.
- Padding: CBC operates on fixed-size blocks (16 bytes for AES). If your data isn’t a multiple of the block size, you need to add padding. This is where padding oracle attacks often occur if not handled carefully.
Example in Python (using cryptography library):
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 key (256-bit)
key = os.urandom(32)
# Generate a random IV (16-byte for CBC)
iv = os.urandom(16)
# Data to encrypt
plaintext = b"This is a message for CBC."
# --- Encryption ---
# PKCS7 padding
padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_plaintext = padder.update(plaintext) + padder.finalize()
# Create cipher object
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize()
print(f"Ciphertext (CBC): {ciphertext.hex()}")
# --- Decryption ---
decryptor_cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
decryptor = decryptor_cipher.decryptor()
decrypted_padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
# Unpad the data
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
decrypted_plaintext = unpadder.update(decrypted_padded_plaintext) + unpadder.finalize()
print(f"Decrypted plaintext (CBC): {decrypted_plaintext.decode()}")
Important Note: The above CBC example only shows encryption/decryption. To make it secure for real-world use, you’d need to:
- Generate the
ivand send it along with the ciphertext. - Calculate an HMAC of the ciphertext (and potentially IV/associated data) using a separate key and send that too.
- On the receiving end, verify the HMAC before attempting decryption.
AES-CTR: The Building Block for GCM
AES-CTR (Counter) mode is another way to use AES. It turns a block cipher into a stream cipher.
How it works: It encrypts a unique counter value (derived from a nonce and a counter) for each block, and XORs the result with the plaintext.
Key characteristics:
- Confidentiality only: Like CBC, CTR mode itself only provides encryption. It needs a separate MAC for integrity.
- Parallelizable: Encryption and decryption can be done in parallel because each block is independent. This makes it very fast.
- No padding: Because it’s a stream cipher, CTR mode doesn’t require padding.
- Nonce/IV: Requires a nonce that, combined with a counter, must be unique for every block encrypted with the same key. Reusing a nonce/counter pair with the same key is catastrophic.
Example in Python (using cryptography library):
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os
# Generate a random key (256-bit)
key = os.urandom(32)
# Generate a random nonce (e.g., 8-byte)
nonce = os.urandom(8)
# Data to encrypt
plaintext = b"This is a message for CTR."
# --- Encryption ---
# CTR mode usually uses a 16-byte block size, with a portion for nonce and a portion for counter.
# The library handles combining nonce and counter. A common nonce size is 8 bytes, leaving 8 bytes for the counter.
cipher = Cipher(algorithms.AES(key), modes.CTR(nonce), backend=default_backend())
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
print(f"Ciphertext (CTR): {ciphertext.hex()}")
# --- Decryption ---
# Decryption is identical to encryption in CTR mode
decryptor_cipher = Cipher(algorithms.AES(key), modes.CTR(nonce), backend=default_backend())
decryptor = decryptor_cipher.decryptor()
decrypted_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
print(f"Decrypted plaintext (CTR): {decrypted_plaintext.decode()}")
When to use which:
- AES-GCM: Use this for almost all new applications, especially network protocols (TLS 1.2+, SSH), disk encryption, and anywhere you need both confidentiality and integrity efficiently. It’s the modern, recommended choice.
- AES-CBC: If you’re working with legacy systems that require it, be extremely careful. Always combine it with a strong MAC (like HMAC-SHA256) and pay close attention to padding oracle vulnerabilities. For new systems, avoid it if possible.
- AES-CTR: Use this when you need a stream cipher and can implement your own robust MAC on top. It’s a building block for GCM and can be useful in specific scenarios where parallelization is critical and a separate authentication mechanism is already in place or easy to add.
The most surprising thing about these modes is how easily they can be broken by simple mistakes. A reused IV/nonce with GCM or CTR, or a missing MAC with CBC, can lead to complete compromise of your data’s confidentiality and integrity.
The next concept to understand is key management: how you securely generate, distribute, and store the secret keys used by these algorithms.