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

  1. 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.
  2. Log/inspect the JWE alg and enc headers and verify the key your locator returns implements the expected algorithm interface.
  3. If you wrote a custom KeyAlgorithm, ensure getDecryptionKey returns a non-null SecretKey for valid requests or throws a descriptive exception.
  4. 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

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


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)