{"record":{"id":"05ad1ca76747a554","repo":"spring-projects/spring-security","slug":"cannot-decrypt-05ad1c","errorCode":null,"errorMessage":"Cannot decrypt","messagePattern":"Cannot decrypt","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java","lineNumber":234,"sourceCode":"\t\tByteArrayOutputStream output = new ByteArrayOutputStream(text.length);\n\t\ttry {\n\t\t\tint length = readInt(input);\n\t\t\tbyte[] random = new byte[length];\n\t\t\tinput.read(random);\n\t\t\tfinal Cipher cipher = Cipher.getInstance(alg.getJceName());\n\t\t\tcipher.init(Cipher.DECRYPT_MODE, key);\n\t\t\tString secret = new String(Hex.encode(cipher.doFinal(random)));\n\t\t\tbyte[] buffer = new byte[text.length - random.length - 2];\n\t\t\tinput.read(buffer);\n\t\t\tBytesEncryptor aes = gcm ? Encryptors.stronger(secret, salt) : Encryptors.standard(secret, salt);\n\t\t\toutput.write(aes.decrypt(buffer));\n\t\t\treturn output.toByteArray();\n\t\t}\n\t\tcatch (RuntimeException ex) {\n\t\t\tthrow ex;\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tthrow new IllegalStateException(\"Cannot decrypt\", ex);\n\t\t}\n\t}\n\n\tprivate static boolean isHex(String input) {\n\t\ttry {\n\t\t\tHex.decode(input);\n\t\t\treturn true;\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tpublic boolean canDecrypt() {\n\t\treturn this.privateKey != null;\n\t}\n\n}","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/crypto/src/main/java/org/springframework/security/crypto/encrypt/RsaSecretEncryptor.java#L216-L252","documentation":"RsaSecretEncryptor.decrypt wraps any non-RuntimeException thrown during RSA decryption (JCE Cipher/BadPadding/IllegalBlockSizeException etc.) in an IllegalStateException with the message 'Cannot decrypt'. The library throws it because the input could not be decrypted with the configured RSA key — typically wrong key, corrupted/ciphertext mismatch, or a cipher operation failure. It deliberately preserves the original exception as the cause.","triggerScenarios":"Calling decrypt() on ciphertext produced with a different key or algorithm; RSA cipher failure such as BadPaddingException/IllegalBlockSizeException because the data is not valid RSA ciphertext for this encryptor; using an encryptor whose key pair does not match the one used to encrypt; decryption path taken when the input was actually encrypted with the public-key-only mode or vice versa (see tests roundTripWithMixedAlgorithm, roundTripWithPublicKeyEncryption, publicKeyCannotDecrypt).","commonSituations":"Rotating or regenerating the keystore/KeyPair while old ciphertexts remain in the database; sharing ciphertext between environments with different keys; decrypting base64/hex-decoded data that was encrypted by another library or algorithm; a keystore alias pointing to the wrong private key.","solutions":["Ensure decryption uses the exact same RsaSecretEncryptor/key pair that produced the ciphertext (same keystore, alias, and key).","Verify the input is the raw ciphertext in the form the encryptor expects (bytes, not re-encoded hex/base64 unless matching how it was encrypted).","Check that encrypt/decrypt use the same algorithm configuration; mixed-algorithm round trips fail by design (public key cannot decrypt its own output).","Catch IllegalStateException and inspect getCause() to distinguish BadPadding/IllegalBlockSize (wrong key/data) from other failures.","If keys were rotated, re-encrypt data with the new key before discarding the old one."],"exampleFix":"// before\nString plain = new String(encryptor.decrypt(cipherText));\n// after\ntry {\n    String plain = new String(encryptor.decrypt(cipherText));\n} catch (IllegalStateException ex) {\n    // ex.getCause() is the JCE failure: usually wrong key or corrupt ciphertext\n    throw new DecryptionException(\"Ciphertext not decryptable with configured RSA key\", ex);\n}","handlingStrategy":"try-catch","validationCode":"// Before decrypting, confirm you hold the same encryptor/key that encrypted the data\nif (ciphertext == null || ciphertext.length == 0) {\n    throw new IllegalArgumentException(\"no ciphertext to decrypt\");\n}\n// If ciphertext was stored as text, decode it exactly as it was encoded on write (hex vs base64)","typeGuard":"boolean canDecrypt(RsaSecretEncryptor e, byte[] stored, byte[] probe) {\n    try { e.decrypt(e.encrypt(new byte[0])); return true; } catch (Exception ex) { return false; }\n}","tryCatchPattern":"try {\n    byte[] plain = encryptor.decrypt(ciphertext);\n} catch (IllegalStateException ex) {\n    // cause is the JCE failure (wrong key / corrupt data)\n    logger.warn(\"Decryption failed: {}\", ex.getCause() == null ? ex.getMessage() : ex.getCause().toString());\n    throw new DecryptionException(ex);\n}","preventionTips":["Store the key/keystore version alongside ciphertext so the right encryptor can be selected on decrypt","Never rotate keys without a re-encryption migration for existing data","Do not re-encode ciphertext (hex/base64) inconsistently between encrypt and decrypt paths","Log only ex.getCause() type, never ciphertext or keys, when handling this error","Add round-trip encrypt/decrypt tests for each environment's key configuration"],"tags":["rsa","crypto","decryption","keystore"],"backgroundTag":"decryption-failed","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}