{"record":{"id":"ab0f1e0a889c6e94","repo":"apache/iceberg","slug":"failed-to-encrypt","errorCode":null,"errorMessage":"Failed to encrypt","messagePattern":"Failed to encrypt","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/apache/iceberg/encryption/Ciphers.java","lineNumber":114,"sourceCode":"        // doFinal encrypts and adds a GCM tag. The nonce is added later.\n        enciphered =\n            cipher.doFinal(\n                plaintext,\n                plaintextOffset,\n                plaintextLength,\n                ciphertextBuffer,\n                ciphertextOffset + NONCE_LENGTH);\n\n        if (enciphered != plaintextLength + GCM_TAG_LENGTH) {\n          throw new RuntimeException(\n              \"Failed to encrypt block: expected \"\n                  + plaintextLength\n                  + GCM_TAG_LENGTH\n                  + \" encrypted bytes but produced bytes \"\n                  + enciphered);\n        }\n      } catch (GeneralSecurityException e) {\n        throw new RuntimeException(\"Failed to encrypt\", e);\n      }\n\n      // Add the nonce\n      System.arraycopy(nonce, 0, ciphertextBuffer, ciphertextOffset, NONCE_LENGTH);\n\n      return enciphered + NONCE_LENGTH;\n    }\n  }\n\n  public static class AesGcmDecryptor {\n    private final SecretKeySpec aesKey;\n    private final Cipher cipher;\n\n    public AesGcmDecryptor(byte[] keyBytes) {\n      this.aesKey = newKey(keyBytes);\n      this.cipher = newCipher();\n    }\n","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/encryption/Ciphers.java#L96-L132","documentation":"Post-condition failure in Ciphers.encrypt: after AES-GCM doFinal wrote into the ciphertext buffer (with room left for the nonce), the number of bytes produced does not equal plaintextLength + GCM tag length. This is a defensive sanity check on JCE cipher output — it cannot be triggered by plaintext content and indicates corrupted cipher state or a JVM crypto mismatch.","triggerScenarios":"Cipher.getInstance/init/doFinal throwing GeneralSecurityException during encrypt() — e.g. an invalid AES key length, a restricted (crypto-policy-limited) JVM, or provider initialization failure.","commonSituations":"Older JDKs with limited-strength crypto policy and 256-bit keys; a key that isn't a valid AES key size (16/24/32 bytes); missing JCE unlimited-strength policy files on legacy Java 8.","solutions":["Check the wrapped cause (getCause()) for the real GeneralSecurityException","Ensure the key is exactly 16, 24, or 32 bytes","Upgrade to a modern JDK without export crypto restrictions","Verify a functioning AES/GCM provider is installed"],"exampleFix":"// inspect the real cause\ntry { ... } catch (RuntimeException e) {\n  log.error(\"encrypt failed\", e.getCause());\n}","handlingStrategy":"try-catch","validationCode":"// pre-check key material\nbyte[] key = keySelector.keyFor(file);\nif (key == null || !(key.length == 16 || key.length == 24 || key.length == 32)) {\n  throw new IllegalArgumentException(\"AES key must be 16/24/32 bytes, got \" + (key == null ? \"null\" : key.length));\n}","typeGuard":null,"tryCatchPattern":"try {\n  cipherOut.write(data);\n} catch (RuntimeException e) {\n  if (\"Failed to encrypt\".equals(e.getMessage())) {\n    Throwable cause = e.getCause(); // GeneralSecurityException with the real reason\n    throw new IllegalStateException(\"Encryption failed: \" + cause, e);\n  }\n  throw e;\n}","preventionTips":["Always pass keys of exactly 16, 24, or 32 bytes","Decode base64 keys exactly once and verify length before use","Run a modern JDK without restricted crypto policy"],"tags":["encryption","crypto","jce"],"backgroundTag":"encryption-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"}