Symmetric encryption

Handle nonces and associated data

Follow the library’s nonce requirements and authenticate unencrypted context such as record IDs, versions, and content types.

A nonce is a “number used once”. It is not normally secret — you store it right next to the ciphertext — but reuse can destroy security for common encryption constructions.

For AES-GCM the rule is absolute: never reuse the same nonce with the same key. Two messages encrypted with the same key and nonce leak the XOR of their plaintexts, and nonce reuse can also let an attacker forge authentication tags. One repeated pair can compromise the key’s entire authentication guarantee, not just the two messages involved.

How reuse happens in real systems

Nobody reuses a nonce on purpose. It happens through coordination failures. Two workers share an encryption key and each starts a nonce counter at zero after deployment. Their first messages reuse a nonce with the same key, breaking the construction assumptions.

Follow the exact API contract. Prefer library-managed nonces. When you must provide one, generate or count it according to the construction:

import { randomBytes } from 'node:crypto'

const nonce = randomBytes(12)   // fresh random 96-bit nonce per encryption

Random 12-byte nonces are safe for AES-GCM as long as a single key does not encrypt a very large number of messages; NIST guidance caps random-nonce use at 2^32 encryptions per key, and staying far below that is the comfortable position. Counters avoid collision entirely but demand durable, coordinated state — exactly what the two-workers failure lacked. When in doubt, use random nonces and rotate keys before the volume gets large.

Associated data binds context

AEAD APIs accept a second input: associated data (AAD). It remains visible — it is not encrypted — but it is protected from undetected modification, because the authentication tag covers it too.

Use it to bind ciphertext to its context, such as a record ID and schema version:

const cipher = createCipheriv('aes-256-gcm', key, nonce)
cipher.setAAD(Buffer.from('user:4821:v2'))
const ciphertext = Buffer.concat([cipher.update(address), cipher.final()])

Decryption must present the same AAD or the tag check fails. This prevents moving valid ciphertext to another record even though that context is not encrypted: an attacker copying user 4821’s encrypted address onto user 977’s row gets an authentication failure, not a silent swap.

Practice

Use a library-managed nonce to encrypt two records and save the nonce, record ID, and schema version with each result. Swap the record IDs during decryption and prove authentication fails. Then simulate two workers or a counter restart and show how the design prevents nonce reuse under the same key.

Lesson completed

Take this course offline

Get every free book, course edition, and software download.

Get the download library →