apache/iceberg · error · RuntimeException

Failed to decrypt

Error message

Failed to decrypt

What it means

Ciphers.decrypt wraps any GeneralSecurityException other than the bad-tag case (which gets its own message) in a RuntimeException('Failed to decrypt') with the original exception chained as the cause.

Solutions

  1. Inspect getCause() for the underlying GeneralSecurityException
  2. Validate that the key is 16/24/32 bytes and correctly decoded (single base64 decode)
  3. Upgrade/fix the JDK security providers
Defensive patterns

Strategy: try-catch

Validate before calling

if (key == null || !(key.length == 16 || key.length == 24 || key.length == 32)) {
  throw new IllegalArgumentException("invalid AES key length: " + (key == null ? "null" : key.length));
}

Try / catch

try {
  cipher.decrypt(...);
} catch (RuntimeException e) {
  if ("Failed to decrypt".equals(e.getMessage())) {
    log.error("decrypt failed, cause: {}", e.getCause());
  }
  throw e;
}

Prevention

When it happens

Trigger: Cipher init/update/doFinal during decrypt() throwing GeneralSecurityException — typically invalid key material (wrong AES key size), invalid algorithm parameters, or provider failure.

Common situations: Passing a raw envelope data key of the wrong length; a restricted JCE policy JVM; corrupted key bytes decoded from KMS with wrong encoding (e.g. base64 decoded twice).

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/578a21d5fc6a8489. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/org/apache/iceberg/encryption/Ciphers.java:181

        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);
    return new SecretKeySpec(keyBytes, "AES");
  }

  private static Cipher newCipher() {
    try {

View on GitHub (pinned to 86d9c8fc54)