{"record":{"id":"0750435ed56d3e5b","repo":"spring-projects/spring-security","slug":"unable-to-encrypt-decrypt","errorCode":null,"errorMessage":"unable to encrypt/decrypt","messagePattern":"unable to encrypt/decrypt","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java","lineNumber":76,"sourceCode":"\n\t@Override\n\tpublic byte[] decrypt(byte[] encryptedBytes) {\n\t\tCBCModeCipher cbcModeCipher = CBCBlockCipher.newInstance(AESEngine.newInstance());\n\t\tbyte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength());\n\t\tencryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length);\n\t\tPaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(cbcModeCipher, new PKCS7Padding());\n\t\tblockCipher.init(false, new ParametersWithIV(this.secretKey, iv));\n\t\treturn process(blockCipher, encryptedBytes);\n\t}\n\n\tprivate byte[] process(BufferedBlockCipher blockCipher, byte[] in) {\n\t\tbyte[] buf = new byte[blockCipher.getOutputSize(in.length)];\n\t\tint bytesWritten = blockCipher.processBytes(in, 0, in.length, buf, 0);\n\t\ttry {\n\t\t\tbytesWritten += blockCipher.doFinal(buf, bytesWritten);\n\t\t}\n\t\tcatch (InvalidCipherTextException ex) {\n\t\t\tthrow new IllegalStateException(\"unable to encrypt/decrypt\", ex);\n\t\t}\n\t\tif (bytesWritten == buf.length) {\n\t\t\treturn buf;\n\t\t}\n\t\tbyte[] out = new byte[bytesWritten];\n\t\tSystem.arraycopy(buf, 0, out, 0, bytesWritten);\n\t\treturn out;\n\t}\n\n}\n","sourceCodeStart":58,"sourceCodeEnd":87,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java#L58-L87","documentation":"Thrown by BouncyCastleAesCbcBytesEncryptor.process when the underlying BouncyCastle CBC cipher rejects the ciphertext during doFinal (InvalidCipherTextException). During decryption this almost always means the ciphertext is corrupt, truncated, or was produced with a different key/password/IV strategy. The library wraps it as IllegalStateException because the input is assumed to be internally generated.","triggerScenarios":"Calling decrypt(bytes) on data that was not produced by the same encryptor instance/configuration: different password or salt, tampered or truncated byte array, base64/URL-decoding errors corrupting the bytes, or attempting to decrypt data encrypted with a different algorithm (e.g. AES/GCM output fed to the CBC encryptor).","commonSituations":"Rotating the password or salt in application properties while old encrypted values remain in the database; copying encrypted values between environments with different keys; manually editing or re-encoding stored ciphertext; decrypting data produced by another tool with different padding/block handling.","solutions":["Verify the decryptor uses exactly the same password CharSequence and salt bytes as the encryptor that produced the data.","Check the ciphertext is intact and correctly encoded: if stored base64, ensure decode with Base64.getDecoder() (not URL decoder) and no truncation.","If keys were rotated, re-encrypt old data with the previous key before switching, or migrate records in a batch job.","Wrap decrypt calls in try-catch and treat failure as 'not encrypted with current key' rather than crashing.","Confirm you are not mixing BouncyCastleAesCbcBytesEncryptor and BouncyCastleAesGcmBytesEncryptor output."],"exampleFix":"// before\nString decrypted = new String(encoder.decrypt(Base64.getUrlDecoder().decode(token)));\n// after\nbyte[] cipherBytes = Base64.getDecoder().decode(token);\ntry {\n    String decrypted = new String(encoder.decrypt(cipherBytes), StandardCharsets.UTF_8);\n} catch (IllegalStateException ex) {\n    throw new BadCredentialsException(\"Value not encrypted with current key\", ex);\n}","handlingStrategy":"try-catch","validationCode":"// no pre-call validation possible for crypto; verify key/salt equality at startup\nassert Arrays.equals(encryptionSalt, decryptionSalt);","typeGuard":null,"tryCatchPattern":"try {\n    byte[] plain = encryptor.decrypt(cipherBytes);\n} catch (IllegalStateException ex) {\n    throw new BadCredentialsException(\"Value not encrypted with current key\", ex);\n}","preventionTips":["Store password and salt in one config source used by both encrypt and decrypt paths.","Always Base64 encode ciphertext before storing/transmitting as text.","Version ciphertext records so key rotation can detect old-key data.","Add integration tests that round-trip encrypt->decrypt with production config."],"tags":["crypto","encryption","decryption","ciphertext-corrupt","spring-security"],"backgroundTag":"invalid-argument-value","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}