AES-GCM
AES-GCM (Galois/Counter Mode) is the modern authenticated encryption default. Encrypts + authenticates in one operation; tampering breaks the auth tag and decryption refuses to return data.
Encrypt, decrypt, key management
EXAMPLE
// 1) Node — encrypt/decrypt with a 256-bit key
import crypto from 'node:crypto';
function encrypt(plaintext, key) {
const iv = crypto.randomBytes(12); // GCM nonce — 12 bytes, UNIQUE per encryption
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
const authTag = cipher.getAuthTag(); // 16 bytes
// Pack as: iv || ciphertext || authTag
return Buffer.concat([iv, ciphertext, authTag]).toString('base64');
}
function decrypt(packed, key) {
const buf = Buffer.from(packed, 'base64');
const iv = buf.subarray(0, 12);
const tag = buf.subarray(buf.length - 16);
const ct = buf.subarray(12, buf.length - 16);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(ct), decipher.final()]).toString('utf8');
}
const key = crypto.randomBytes(32); // KEEP THIS SAFE
const c = encrypt('hello world', key);
console.log(decrypt(c, key)); // 'hello world'
// 2) Add authenticated additional data (AAD) — context that must match
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
cipher.setAAD(Buffer.from(JSON.stringify({ userId: 42 })));
// On decrypt, you MUST setAAD with the same value before final()
// 3) Python — cryptography library
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
key = AESGCM.generate_key(bit_length=256)
aes = AESGCM(key)
nonce = os.urandom(12)
ct = aes.encrypt(nonce, b'hello world', associated_data=b'ctx')
pt = aes.decrypt(nonce, ct, associated_data=b'ctx')
# 4) PHP — sodium_crypto_aead_aes256gcm_* (or use ChaCha20-Poly1305 — safer default)
$key = sodium_crypto_aead_aes256gcm_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES);
$ct = sodium_crypto_aead_aes256gcm_encrypt('hello', '', $nonce, $key);
$pt = sodium_crypto_aead_aes256gcm_decrypt($ct, '', $nonce, $key);
# 5) ChaCha20-Poly1305 — strongly preferred when AES hardware is uncertain
# (mobile + IoT). API is the same shape; same nonce rules.
# 6) GCM nonce rules — VIOLATE AT YOUR PERIL
# • Never reuse a nonce with the same key (catastrophic — leaks plaintext XOR)
# • Random 96-bit nonces are safe for ~2^32 messages per key
# • For more messages, use a counter nonce + epoch
# 7) Key management — the actual hard part
# • Generate keys with a CSPRNG (above) — never derive from a password
# • Store keys in a KMS (AWS KMS, GCP KMS, HashiCorp Vault)
# • Rotate keys: store key id alongside ciphertext for forward decryption
# • Wrap data keys with a master key — envelope encryption
# 8) Envelope encryption (the AWS pattern)
# 1) DEK = KMS.GenerateDataKey() → plaintext + encrypted forms
# 2) Encrypt your data with the plaintext DEK
# 3) Store ciphertext + encrypted DEK; throw away plaintext DEK
# 4) To decrypt: KMS.Decrypt(encryptedDEK) → plaintext DEK → decrypt data
# 9) AVOID
# • CBC + HMAC by hand — easy to mess up
# • ECB — leaks structure (the famous penguin)
# • Custom cipher modes
# • Sharing the nonce across messages
Why it matters
Authenticated encryption (AES-GCM, ChaCha20-Poly1305) is the only acceptable symmetric cipher mode in new code. It catches tampering for free; unauthenticated modes are footguns waiting for the wrong PR.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Browser WebCrypto
const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt','decrypt']);
const iv = crypto.getRandomValues(new Uint8Array(12));
const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, new TextEncoder().encode('hi'));
// NEVER reuse an IV with the same key — catastrophic.
Try it Yourself »
Exercise
IV size for AES-GCM, in bytes.
const iv = crypto.getRandomValues(new Uint8Array(
));
Two digits.
Discussion
Loading…