apache/iceberg · critical · RuntimeException
GCM tag check failed. Possible reasons: wrong decryption…
Error message
GCM tag check failed. Possible reasons: wrong decryption key; or corrupt/tampered data. AES GCM doesn't differentiate between these two.
What it means
During GCM decryption the AEADBadTagException means the 16-byte authentication tag computed over (ciphertext + AAD) does not match the tag stored with the data. Since GCM's tag binds both key and data, this indicates either the wrong decryption key or corrupt/tampered ciphertext — AES-GCM cannot distinguish the two, and Iceberg says so explicitly.
Solutions
- Verify the decryption key matches the one used at write time (check KMS key ID / envelope key material)
- Confirm the file is intact: re-download/verify checksums, check for truncation
- Ensure the AAD prefix (fileAadPrefix) and block index used at read match those at write
- Confirm you're decrypting the correct byte range including the trailing GCM tag
Defensive patterns
Strategy: try-catch
Validate before calling
// verify key identity and file integrity before decrypting
if (!keyIdUsedAtWrite.equals(currentKeyId)) {
throw new IllegalStateException("Key mismatch: file encrypted with " + keyIdUsedAtWrite);
} Try / catch
try {
len = cipher.decrypt(ciphertext, off, clen, plain, poff, aadPrefix);
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().startsWith("GCM tag check failed")) {
// wrong key or corrupt data: surface key-id/file-checksum info to the operator
throw new DataIntegrityException("Verify key id and file checksum for encrypted file", e);
}
throw e;
} Prevention
- Store the KMS key id with the file metadata and assert it matches at read time
- Never mutate ciphertext bytes; copy files atomically and verify checksums
- Keep the AAD prefix consistent between write and read paths
- Guard against truncated reads: confirm the byte range includes the trailing GCM tag
When it happens
Trigger: Calling Ciphers.decrypt with a key different from the one used to encrypt; ciphertext bytes modified in transit or at rest; nonce/AAD prefix mismatch (wrong fileAadPrefix); decrypting a region that includes or excludes the wrong offset so the tag bytes are misread.
Common situations: Rotating KMS keys or data keys so old files decrypt with a new key; copying/syncing encrypted files partially (truncated writes); reading a file written by another cluster with a different envelope key; storage-layer bit corruption.
Related errors
- Failed to create GCM cipher
- Failed to decrypt
- Failed to encrypt
- Failed to encrypt block: expected
- Full metadata integrity check skipped because no metadata…
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/e5e005d2a3b6132e.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/apache/iceberg/encryption/Ciphers.java:176
int plaintextLength;
try {
GCMParameterSpec spec =
new GCMParameterSpec(GCM_TAG_LENGTH_BITS, ciphertext, ciphertextOffset, NONCE_LENGTH);
cipher.init(Cipher.DECRYPT_MODE, aesKey, spec);
if (null != aad) {
cipher.updateAAD(aad);
}
// For java Cipher, the nonce is not part of ciphertext
plaintextLength =
cipher.doFinal(
ciphertext,
ciphertextOffset + NONCE_LENGTH,
ciphertextLength - NONCE_LENGTH,
plaintextBuffer,
plaintextOffset);
} catch (AEADBadTagException e) {
throw new RuntimeException(
"GCM tag check failed. Possible reasons: wrong decryption key; or corrupt/tampered"
+ " data. AES GCM doesn't differentiate between these two.",
e);
} catch (GeneralSecurityException e) {
throw new RuntimeException("Failed to decrypt", e);
}
return plaintextLength;
}
}
private static SecretKeySpec newKey(byte[] keyBytes) {
Preconditions.checkArgument(keyBytes != null, "Invalid key: null");
int keyLength = keyBytes.length;
Preconditions.checkArgument(
(keyLength == 16 || keyLength == 24 || keyLength == 32),
"Invalid key length: %s (must be 16, 24, or 32 bytes)",
keyLength);View on GitHub (pinned to 86d9c8fc54)