spring-projects/spring-security · error · IllegalStateException
unable to encrypt/decrypt
Error message
unable to encrypt/decrypt
What it means
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.
Source
Thrown at crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesCbcBytesEncryptor.java:76
@Override
public byte[] decrypt(byte[] encryptedBytes) {
CBCModeCipher cbcModeCipher = CBCBlockCipher.newInstance(AESEngine.newInstance());
byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength());
encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length);
PaddedBufferedBlockCipher blockCipher = new PaddedBufferedBlockCipher(cbcModeCipher, new PKCS7Padding());
blockCipher.init(false, new ParametersWithIV(this.secretKey, iv));
return process(blockCipher, encryptedBytes);
}
private byte[] process(BufferedBlockCipher blockCipher, byte[] in) {
byte[] buf = new byte[blockCipher.getOutputSize(in.length)];
int bytesWritten = blockCipher.processBytes(in, 0, in.length, buf, 0);
try {
bytesWritten += blockCipher.doFinal(buf, bytesWritten);
}
catch (InvalidCipherTextException ex) {
throw new IllegalStateException("unable to encrypt/decrypt", ex);
}
if (bytesWritten == buf.length) {
return buf;
}
byte[] out = new byte[bytesWritten];
System.arraycopy(buf, 0, out, 0, bytesWritten);
return out;
}
}
View on GitHub (pinned to 96852e8860)
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.
Example fix
// before
String decrypted = new String(encoder.decrypt(Base64.getUrlDecoder().decode(token)));
// after
byte[] cipherBytes = Base64.getDecoder().decode(token);
try {
String decrypted = new String(encoder.decrypt(cipherBytes), StandardCharsets.UTF_8);
} catch (IllegalStateException ex) {
throw new BadCredentialsException("Value not encrypted with current key", ex);
} Defensive patterns
Strategy: try-catch
Validate before calling
// no pre-call validation possible for crypto; verify key/salt equality at startup assert Arrays.equals(encryptionSalt, decryptionSalt);
Try / catch
try {
byte[] plain = encryptor.decrypt(cipherBytes);
} catch (IllegalStateException ex) {
throw new BadCredentialsException("Value not encrypted with current key", ex);
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Unable to initialize due to invalid decryption parameter spe
- Saml2Exception wrapping DecryptionException during encrypted
- Bad salt length
- Invalid prefix
- Invalid log_rounds
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/0750435ed56d3e5b.
Report an issue: GitHub.