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

  1. Verify the decryption key matches the one used at write time (check KMS key ID / envelope key material)
  2. Confirm the file is intact: re-download/verify checksums, check for truncation
  3. Ensure the AAD prefix (fileAadPrefix) and block index used at read match those at write
  4. 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

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


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)