spring-projects/spring-security · error · IllegalStateException

unable to encrypt/decrypt

Error message

unable to encrypt/decrypt

What it means

Thrown by BouncyCastleAesGcmBytesEncryptor.process when the GCM cipher's doFinal reports an InvalidCipherTextException, i.e. GCM tag verification failed. With AES-GCM this specifically indicates authentication failure: wrong key, corrupted or truncated ciphertext, or mismatched associated data. GCM detects tampering, so this is often a sign of intentional modification or key mismatch.

Source

Thrown at crypto/src/main/java/org/springframework/security/crypto/encrypt/BouncyCastleAesGcmBytesEncryptor.java:72

	}

	@Override
	public byte[] decrypt(byte[] encryptedBytes) {
		byte[] iv = EncodingUtils.subArray(encryptedBytes, 0, this.ivGenerator.getKeyLength());
		encryptedBytes = EncodingUtils.subArray(encryptedBytes, this.ivGenerator.getKeyLength(), encryptedBytes.length);
		AEADBlockCipher blockCipher = GCMBlockCipher.newInstance(AESEngine.newInstance());
		blockCipher.init(false, new AEADParameters(this.secretKey, 128, iv, null));
		return process(blockCipher, encryptedBytes);
	}

	private byte[] process(AEADBlockCipher 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. Ensure the same password and secure-random salt bytes are used on both encrypt and decrypt paths.
  2. If ciphertext travels through text channels, always Base64-encode/decode — never new String(bytes) round-trips.
  3. Re-encrypt stored data if the key was rotated; keep old key available for migration.
  4. Treat the failure as tampered/unrecognized input: catch and return an authentication error.
  5. Verify you decrypt with the same encryptor class that encrypted (CBC vs GCM outputs are incompatible).

Example fix

// before
byte[] plain = gcmEncryptor.decrypt(rawBytesFromRequest);
// after
try {
    byte[] plain = gcmEncryptor.decrypt(rawBytesFromRequest);
} catch (IllegalStateException ex) {
    throw new SecurityException("Ciphertext failed GCM authentication (wrong key or tampered)", ex);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// verify ciphertext integrity markers before decrypt
if (cipherBytes == null || cipherBytes.length < 28) throw new IllegalArgumentException("ciphertext too short");

Try / catch

try {
    return gcmEncryptor.decrypt(cipherBytes);
} catch (IllegalStateException ex) {
    throw new SecurityException("GCM authentication failed: wrong key or tampered data", ex);
}

Prevention

When it happens

Trigger: Calling decrypt on bytes whose GCM authentication tag does not verify: different password/salt than encryption, bit rot or truncation of stored ciphertext, tampered token, or decrypting CBC-encrypted data with the GCM encryptor.

Common situations: Password or secret rotated without re-encrypting stored values; encrypted cookie/JWT-like tokens modified by a client; data passed through systems that alter bytes (e.g. treating binary as a String instead of Base64); cross-environment data copies.

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/52a3f8e6787419e3. Report an issue: GitHub.