HackBlue

Tools · Cryptography and passwords in practice · 11 / 20

AES-GCM authenticated encryption in Python: keys, nonces, and associated data

Encrypt and decrypt with AES-256-GCM using the cryptography library, understand why a nonce must never repeat with the same key, and use associated data to bind context.

11 min read · practice only with authorization

Authenticated encryption

AES-GCM provides confidentiality and integrity together: decryption fails if the ciphertext, nonce, tag, or associated data were changed. That is what you want for almost every application use of symmetric encryption. ChaCha20-Poly1305 is an equally good alternative, faster on devices without AES hardware.

The nonce rule

GCM uses a 96-bit nonce that must be unique for every encryption with the same key. Reusing a nonce with the same key reveals the XOR of the two plaintexts and lets an attacker forge messages. Generate a random 12-byte nonce per message with os.urandom and store it with the ciphertext; it does not need to be secret. For very high message counts under one key, use counters or rotate keys, or use AES-GCM-SIV, which tolerates nonce reuse better.

Lab: encrypt, tamper, and bind context

Install cryptography, generate a key, encrypt a record with associated data naming the user ID, and decrypt it. Then try three tamper tests: change a ciphertext byte, change the associated data to another user ID, and change the nonce. Each must raise InvalidTag.

Snippet
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.exceptions import InvalidTag

key = AESGCM.generate_key(bit_length=256)
aead = AESGCM(key)
nonce = os.urandom(12)
aad = b'user_id=1001'
ct = aead.encrypt(nonce, b'card ending 4242', aad)
print(aead.decrypt(nonce, ct, aad))

for label, n, c, a in [
    ('ciphertext', nonce, bytes([ct[0] ^ 1]) + ct[1:], aad),
    ('aad', nonce, ct, b'user_id=1002'),
    ('nonce', os.urandom(12), ct, aad),
]:
    try:
        aead.decrypt(n, c, a)
    except InvalidTag:
        print('tamper detected:', label)

record = nonce + ct   # store nonce alongside ciphertext

Key handling

The key is the secret. Generate it with a CSPRNG, store it in a KMS or secret manager, and use envelope encryption for data at rest: a data key encrypts the data, and a KMS key encrypts the data key. Derive keys from passwords only through a password KDF such as Argon2id or scrypt, never by hashing the password once.

Diagram

What AES-GCM checks on decrypt

Change anything and decryption fails.

noncetagAAD

Original

nonce N, ciphertext C, AAD user_id=1001
Plaintext returned

Tampered AAD

nonce N, ciphertext C, AAD user_id=1002
InvalidTag

Reused nonce

Same key and nonce for two messages
Both decrypt, but security is broken

Check yourself

1. What is the most important rule for GCM nonces?

2. Decrypting with different associated data than was used to encrypt results in what?

3. Where should the nonce be stored?