AES-GCM associated data is authenticated with the ciphertext but is not encrypted into it.
Java AES-GCM associated data: bind ciphertext to its record context
Tie the payload to its owner
A sealed payment note should not be movable from one account context to another without detection. Feed a canonical account identifier through updateAAD before doFinal on both sides. Decryption with a different identifier fails tag verification even when the key, nonce and ciphertext are unchanged.
The fixture checks one matching and one mismatched context. It does not serialize the context or key version; a production record must define and persist enough metadata to reproduce the exact AAD bytes after restart.
Order API calls correctly
Call updateAAD before processing plaintext or ciphertext for a GCM operation. Apply identical byte encoding and field framing at both ends. HMAC field framing shows why ambiguous concatenation can undermine a context definition.
Working program
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import javax.crypto.AEADBadTagException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
public class AccountNoteContext {
static byte[] open(SecretKey key, byte[] nonce, byte[] sealed, String account) throws Exception {
Cipher decrypt = Cipher.getInstance("AES/GCM/NoPadding");
decrypt.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, nonce));
decrypt.updateAAD(account.getBytes(StandardCharsets.UTF_8));
return decrypt.doFinal(sealed);
}
public static void main(String[] args) throws Exception {
KeyGenerator keys = KeyGenerator.getInstance("AES");
keys.init(128);
SecretKey key = keys.generateKey();
byte[] nonce = new byte[12];
new SecureRandom().nextBytes(nonce);
Cipher encrypt = Cipher.getInstance("AES/GCM/NoPadding");
encrypt.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, nonce));
encrypt.updateAAD("account-47".getBytes(StandardCharsets.UTF_8));
byte[] sealed = encrypt.doFinal("approved".getBytes(StandardCharsets.UTF_8));
System.out.println("accepted=" + "approved".equals(new String(open(key, nonce, sealed, "account-47"), StandardCharsets.UTF_8)));
try { open(key, nonce, sealed, "account-48"); }
catch (AEADBadTagException rejected) { System.out.println("wrong context rejected"); }
}
}Output
accepted=true
wrong context rejectedCost and ownership
Authentication work grows with ciphertext and associated-data length. The AAD is not secret; it needs canonical bytes and a stable storage format. A new encryption operation needs a new nonce under the same key even when the plaintext or account changes.
Common Mistakes
- Do not confuse authenticated associated data with encrypted data.
- Do not change AAD encoding between encryption and decryption.
- Do not reuse a GCM nonce under one key for a different account.
Read next
Java AES-GCM: reject a changed ciphertext before accepting plaintext, Java HMAC field framing: prevent ambiguous concatenation, Java Unicode: code units, code points and UTF-8 bytes, Java SecureRandom token encoding: preserve unpredictable bytes in a URL-safe form.
