{"record":{"id":"e5e005d2a3b6132e","repo":"apache/iceberg","slug":"gcm-tag-check-failed-possible-reasons-wrong-decr","errorCode":null,"errorMessage":"GCM tag check failed. Possible reasons: wrong decryption key; or corrupt/tampered data. AES GCM doesn't differentiate between these two.","messagePattern":"GCM tag check failed\\. Possible reasons: wrong decryption key; or corrupt/tampered data\\. AES GCM doesn't differentiate between these two\\.","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"critical","filePath":"core/src/main/java/org/apache/iceberg/encryption/Ciphers.java","lineNumber":176,"sourceCode":"      int plaintextLength;\n\n      try {\n        GCMParameterSpec spec =\n            new GCMParameterSpec(GCM_TAG_LENGTH_BITS, ciphertext, ciphertextOffset, NONCE_LENGTH);\n        cipher.init(Cipher.DECRYPT_MODE, aesKey, spec);\n        if (null != aad) {\n          cipher.updateAAD(aad);\n        }\n        // For java Cipher, the nonce is not part of ciphertext\n        plaintextLength =\n            cipher.doFinal(\n                ciphertext,\n                ciphertextOffset + NONCE_LENGTH,\n                ciphertextLength - NONCE_LENGTH,\n                plaintextBuffer,\n                plaintextOffset);\n      } catch (AEADBadTagException e) {\n        throw new RuntimeException(\n            \"GCM tag check failed. Possible reasons: wrong decryption key; or corrupt/tampered\"\n                + \" data. AES GCM doesn't differentiate between these two.\",\n            e);\n      } catch (GeneralSecurityException e) {\n        throw new RuntimeException(\"Failed to decrypt\", e);\n      }\n\n      return plaintextLength;\n    }\n  }\n\n  private static SecretKeySpec newKey(byte[] keyBytes) {\n    Preconditions.checkArgument(keyBytes != null, \"Invalid key: null\");\n    int keyLength = keyBytes.length;\n    Preconditions.checkArgument(\n        (keyLength == 16 || keyLength == 24 || keyLength == 32),\n        \"Invalid key length: %s (must be 16, 24, or 32 bytes)\",\n        keyLength);","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/encryption/Ciphers.java#L158-L194","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// verify key identity and file integrity before decrypting\nif (!keyIdUsedAtWrite.equals(currentKeyId)) {\n  throw new IllegalStateException(\"Key mismatch: file encrypted with \" + keyIdUsedAtWrite);\n}","typeGuard":null,"tryCatchPattern":"try {\n  len = cipher.decrypt(ciphertext, off, clen, plain, poff, aadPrefix);\n} catch (RuntimeException e) {\n  if (e.getMessage() != null && e.getMessage().startsWith(\"GCM tag check failed\")) {\n    // wrong key or corrupt data: surface key-id/file-checksum info to the operator\n    throw new DataIntegrityException(\"Verify key id and file checksum for encrypted file\", e);\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["encryption","crypto","integrity","gcm"],"backgroundTag":"gcm-tag-verification-failed","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}