Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java AES-GCM associated data: bind ciphertext to its record context

Last updated: 1 Oct 20264 min read
tutorial
AdvancedBy AITrove Editorial

AES-GCM associated data is authenticated with the ciphertext but is not encrypted into it.

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

Java
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

Output
accepted=true
wrong context rejected

Cost 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.

java
cryptography
aes-gcm-associated-data
Storage details