Cryptography Best Practices for Developers

Chapter 2 · Practice 3 of 4

Bind ciphertext to its context with associated data

From Encrypting data with AEAD

Why it matters

An AEAD tag can also cover associated data: values that are not encrypted but must not change. Without it, an attacker who can write to your database can swap encrypted fields between rows. User A's encrypted bank account number moved into user B's row still decrypts perfectly, because it is a valid ciphertext under the same key.

Put the context into the associated data so the ciphertext only decrypts where it belongs:

  • the table and column (users.iban)
  • the record ID or tenant ID
  • a schema or format version

Then decryption fails if the ciphertext is moved, which turns a silent data swap into an error you can alert on.

Keep the associated data deterministic and rebuildable from the row itself. If you change its format, version it, or old rows will stop decrypting.

The same idea applies to files and messages: include the file name, the intended recipient or the message type, so a valid ciphertext cannot be replayed into a different slot.

Example

TypeScript
const aad = Buffer.from(`users.iban:v1:${user.id}`);
const stored = encrypt(dataKey, Buffer.from(iban), aad);

// Later: moving this ciphertext to another user's row makes decrypt() throw
const plain = decrypt(dataKey, stored, Buffer.from(`users.iban:v1:${row.id}`));