jwtk/jjwt · error · IllegalStateException
The ' ' JWE key algorithm did not return a decryption key…
Error message
The '<keyAlg id>' JWE key algorithm did not return a decryption key. Unable to perform '<encAlg id>' decryption.
What it means
After locating a raw key, the parser asks the key algorithm (e.g. RSA-OAEP, A128KW, dir) for the CEK via getDecryptionKey(request). A null CEK means the KeyAlgorithm implementation could not derive a decryption key, so the parser cannot perform the required enc decryption and throws IllegalStateException.
Solutions
- Match the key type to the token's alg header: SecretKey for AES key wrap/dir, RSA PrivateKey for RSA-OAEP/RSA1_5, EC PrivateKey for ECDH-ES.
- Log/inspect the JWE alg and enc headers and verify the key your locator returns implements the expected algorithm interface.
- If you wrote a custom KeyAlgorithm, ensure getDecryptionKey returns a non-null SecretKey for valid requests or throws a descriptive exception.
- Regenerate the token with a producer configured consistently with your recipient key setup.
Example fix
// before: dir alg but asymmetric key provided parser.decryptWith(rsaKeyPair.getPublic()).build().parse(jwe); // token alg=dir // after parser.decryptWith(aesKey).build().parse(jwe); // SecretKey for dir/A128KW
Defensive patterns
Strategy: type-guard
Validate before calling
String alg = header.getJweAlgorithm().getId(); // dir/A128KW/A192KW/A256KW require SecretKey; RSA-OAEP/RSA1_5 require RSA PrivateKey; ECDH-ES requires EC PrivateKey
Type guard
boolean keyMatches(String alg, Key k) {
return (alg.startsWith("A") && k instanceof SecretKey)
|| (alg.startsWith("RSA") && k instanceof java.security.interfaces.RSAPrivateKey)
|| (alg.startsWith("ECDH") && k instanceof java.security.interfaces.ECPrivateKey);
} Try / catch
try { parser.parse(jwe); } catch (IllegalStateException e) { if (e.getMessage().contains("did not return a decryption key")) { /* key/alg mismatch: fix locator */ } } Prevention
- Align issuer and recipient algorithm configuration; decode the alg header and pick keys accordingly.
- Never mix symmetric tokens with asymmetric keys or vice versa.
- For custom KeyAlgorithms, always return a SecretKey or throw a descriptive error — never return null.
- Add tests covering each alg your system accepts.
When it happens
Trigger: The key returned by the locator is the wrong type for the key algorithm (e.g. a SecretKey where RSA-OAEP expects an RSA private key, or vice versa), or a custom KeyAlgorithm returns null from getDecryptionKey.
Common situations: Mixing symmetric and asymmetric algorithms (dir with a non-secret key, RSA wrap with a SecretKey), misconfigured key stores handing back the wrong key type, custom KeyAlgorithm implementations with incomplete getDecryptionKey logic.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Cannot decrypt JWE payload: unable to locate key for JWE…
- Both 'signWith' and 'encryptWith' cannot be specified…
- Ciphertext decryption failed: Authentication tag…
- Compact JWE string represents an encrypted key, but the key…
- Compact JWE strings MUST always contain a payload…
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/c9b36ccafd10e56b.
Report an issue: GitHub.
Appendix: source
Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java:558
Key key = this.keyLocator.locate(jweHeader);
if (key == null) {
String msg = "Cannot decrypt JWE payload: unable to locate key for JWE with header: " + jweHeader;
throw new UnsupportedJwtException(msg);
}
if (key instanceof PublicKey) {
throw new InvalidKeyException(PUB_KEY_DECRYPT_MSG);
}
// extract key-specific provider if necessary;
Provider provider = ProviderKey.getProvider(key, this.provider);
key = ProviderKey.getKey(key); // this must be called after ProviderKey.getProvider
DecryptionKeyRequest<Key> request =
new DefaultDecryptionKeyRequest<>(cekBytes, provider, null, jweHeader, encAlg, key);
final SecretKey cek = keyAlg.getDecryptionKey(request);
if (cek == null) {
String msg = "The '" + keyAlg.getId() + "' JWE key algorithm did not return a decryption key. " +
"Unable to perform '" + encAlg.getId() + "' decryption.";
throw new IllegalStateException(msg);
}
// During decryption, the available Provider applies to the KeyAlgorithm, not the AeadAlgorithm, mostly
// because all JVMs support the standard AeadAlgorithms (especially with BouncyCastle in the classpath).
// As such, the provider here is intentionally omitted (null):
// TODO: add encProvider(Provider) builder method that applies to this request only?
InputStream ciphertext = payload.toInputStream();
ByteArrayOutputStream plaintext = new ByteArrayOutputStream(8192);
DecryptAeadRequest dreq = new DefaultDecryptAeadRequest(ciphertext, cek, aad, iv, digest);
encAlg.decrypt(dreq, plaintext);
payload = new Payload(plaintext.toByteArray(), header.getContentType());
integrityVerified = true; // AEAD performs integrity verification, so no exception = verified
} else if (hasDigest && this.signingKeyResolver == null) { //TODO: for 1.0, remove the == null check
// not using a signing key resolver, so we can verify the signature before reading the payload, which is
// always safer:
JwsHeader jwsHeader = Assert.stateIsInstance(JwsHeader.class, header, "Not a JwsHeader. ");View on GitHub (pinned to fb71496164)