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

  1. Verify the decryptor uses exactly the same password CharSequence and salt bytes as the encryptor that produced the data.
  2. Check the ciphertext is intact and correctly encoded: if stored base64, ensure decode with Base64.getDecoder() (not URL decoder) and no truncation.
  3. If keys were rotated, re-encrypt old data with the previous key before switching, or migrate records in a batch job.
  4. Wrap decrypt calls in try-catch and treat failure as 'not encrypted with current key' rather than crashing.
  5. 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

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


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/0750435ed56d3e5b. Report an issue: GitHub.