skip to content

How do you perform authenticated encryption with AES-GCM in Java, and how is the authentication tag handled on encrypt and decrypt?

level: seniorimportance: must knowfreq 60%

answer

  1. GCM = confidentiality + integrity (AEAD)
  2. GCMParameterSpec(128, 12-byte nonce)
  3. Tag is auto-appended on doFinal
  4. Never reuse (key, nonce) — catastrophic
  5. AEADBadTagException = tampering, reveal nothing
  6. updateAAD before doFinal, replay on decrypt

basics

~20 s

Use "AES/GCM/NoPadding" with a GCMParameterSpec that sets the tag length (usually 128 bits) and a unique 12-byte nonce. On encrypt, doFinal appends the authentication tag to the ciphertext automatically. On decrypt, doFinal verifies the tag and throws AEADBadTagException if the data was tampered with.

solid answer

~50 s

AES-GCM is authenticated encryption (AEAD): it gives confidentiality and integrity in one pass. You init with a GCMParameterSpec(tagBits, nonce) — typically 128-bit tag, 12-byte nonce — plus the SecretKey. The critical rule is nonce uniqueness: never reuse a (key, nonce) pair, or GCM's security collapses (an attacker can recover the auth key and forge messages). Use a random 12-byte nonce per message and store it with the ciphertext. On encrypt, doFinal returns ciphertext with the 16-byte tag appended; the JCE handles the tag for you. You can bind extra unencrypted context with updateAAD before doFinal (additional authenticated data — authenticated but not encrypted). On decrypt, init in DECRYPT_MODE with the same key, nonce, and tag length, replay the same AAD, then doFinal — it recomputes and verifies the tag and throws AEADBadTagException if anything was altered. Treat that exception as a tamper signal and reveal nothing about the plaintext.

code

java · 17 lines
java
// Encrypt: nonce || ciphertextWithTag
byte[] nonce = new byte[12];
SecureRandom.getInstanceStrong().nextBytes(nonce);
Cipher enc = Cipher.getInstance("AES/GCM/NoPadding");
enc.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, nonce));
enc.updateAAD(header);                       // optional context binding
byte[] ctWithTag = enc.doFinal(plaintext);   // 16-byte tag appended

// Decrypt: verify tag, fail closed
Cipher dec = Cipher.getInstance("AES/GCM/NoPadding");
dec.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, nonce));
dec.updateAAD(header);                        // must match encrypt-side AAD
try {
    byte[] plain = dec.doFinal(ctWithTag);    // throws AEADBadTagException if tampered
} catch (AEADBadTagException e) {
    throw new SecurityException("integrity check failed"); // reveal nothing
}

go deeper

for a junior

Knows GCM provides both encryption and integrity and that you use a nonce plus a tag.

for a middle

Can wire up GCMParameterSpec, knows the tag is appended automatically and that AEADBadTagException signals tampering.

for a senior

Articulates the nonce-uniqueness invariant and its failure mode, uses AAD correctly, handles tag verification securely, and manages nonce/key lifecycle.

for a principal

Sets cryptographic policy: nonce-management strategy, key rotation before nonce exhaustion, AEAD everywhere, and reviews for misuse-resistant patterns (e.g. envelope encryption / AES-GCM-SIV when available).

## What problem GCM solves Plain encryption (e.g. AES-CBC) provides **confidentiality** — an eavesdropper can't read the data — but **no integrity**: an attacker can flip ciphertext bits and you'd decrypt corrupted/maliciously-altered plaintext without noticing (the basis of padding-oracle and bit-flipping attacks). **Authenticated encryption with associated data (AEAD)** fixes this by also producing an **authentication tag** — a cryptographic checksum that decryption verifies. **AES-GCM** (Galois/Counter Mode) is the standard AEAD construction over AES. ## Key terms - **Nonce / IV**: a *number used once* per message. GCM uses a 12-byte (96-bit) nonce by convention. It is **not secret** but must be **unique per key**. - **Authentication tag**: a 128-bit (typical) value computed over the ciphertext (and any AAD) using the key. Decryption recomputes it and compares; a mismatch means tampering. - **AAD (Additional Authenticated Data)**: data that is **authenticated but not encrypted** — e.g. a message header, version, or recipient id you want bound to the ciphertext so it can't be swapped. ## Encrypting ```java Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); byte[] nonce = new byte[12]; SecureRandom.getInstanceStrong().nextBytes(nonce); GCMParameterSpec spec = new GCMParameterSpec(128, nonce); // 128-bit tag c.init(Cipher.ENCRYPT_MODE, key, spec); c.updateAAD(headerBytes); // optional, authenticated-only byte[] ct = c.doFinal(plaintext); // tag is appended to ct ``` In the JCE, the tag is **automatically appended** to the ciphertext returned by `doFinal`, so `ct.length == plaintext.length + 16`. You then transmit/store `nonce || ct` (the nonce can be prepended in the clear). ## Decrypting ```java GCMParameterSpec spec = new GCMParameterSpec(128, nonce); // same nonce + tag len c.init(Cipher.DECRYPT_MODE, key, spec); c.updateAAD(headerBytes); // must replay the SAME AAD byte[] pt = c.doFinal(ct); // verifies tag; throws on mismatch ``` `doFinal` recomputes the tag over the ciphertext+AAD and compares it to the appended tag. If they differ — wrong key, corrupted data, tampering, or mismatched AAD — it throws **`AEADBadTagException`** (a subclass of `BadPaddingException`) and yields **no plaintext**. ## The cardinal rule: never reuse a (key, nonce) pair GCM is a CTR-mode stream cipher under the hood. Reusing a nonce with the same key means the same keystream encrypts two messages — XORing the two ciphertexts cancels the keystream and leaks plaintext relationships. Worse, nonce reuse lets an attacker recover GCM's internal **authentication subkey (H)** and **forge** valid tags for arbitrary messages. So: use a fresh random 96-bit nonce per message (random collision risk is negligible up to ~2^32 messages per key), or a strictly increasing counter, and **rotate the key** before the nonce space risks exhaustion. ## Tag length and verification handling - Use a **128-bit tag**; shorter tags weaken forgery resistance. - On `AEADBadTagException`, treat it as a tamper/error condition: log generically, return a uniform failure, and **never emit partial or 'corrupted' plaintext** — GCM already withholds it. ## Streaming caveat For large data you might `update()` then `doFinal()`. With GCM, **never trust output from `update` before `doFinal` succeeds**, because the tag is only checked at `doFinal`; buffer until verification passes.

  • What exactly goes wrong if you reuse a nonce with the same GCM key?
    The keystream repeats, so XORing two ciphertexts reveals the XOR of the plaintexts (confidentiality break). More damaging, an attacker can solve for GCM's authentication subkey H and then forge valid tags for chosen messages, fully breaking integrity. It is a catastrophic, not gradual, failure.
  • What is AAD and when would you use it?
    Additional Authenticated Data is bound into the tag but not encrypted. Use it to authenticate context that must travel in the clear or is stored separately — a message version, content-type, record id, or recipient — so an attacker can't transplant a valid ciphertext into a different context.
  • Why must you not trust output from update() before doFinal() in GCM?
    GCM only verifies the authentication tag during doFinal. Bytes returned by update are unverified; if you act on them and then doFinal throws AEADBadTagException, you've already processed attacker-controlled data. Buffer until doFinal succeeds.

saying these in an interview costs you the question

  • Reusing or deriving a deterministic nonce from non-unique data
  • Using a tag length shorter than 128 bits
  • Catching AEADBadTagException and returning the (corrupted) bytes anyway
  • Forgetting to replay the exact same AAD on decrypt
  • Treating GCM as confidentiality-only and bolting on a separate MAC incorrectly
  • Generating the nonce with a non-CSPRNG (e.g. Random)

context