jwtk/jjwt · error · InvalidKeyException

PublicKeys may not be used to decrypt data. PublicKeys are u

Error message

PublicKeys may not be used to decrypt data. PublicKeys are used to encrypt, and PrivateKeys are used to decrypt.

What it means

Asymmetric JWE encryption uses the recipient's public key to encrypt and the private key to decrypt. If the key locator resolves a PublicKey for decryption, the parser throws InvalidKeyException with PUB_KEY_DECRYPT_MSG because a public key cannot perform cipher decryption.

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java:546

            buf.get(aadBytes);
            InputStream aad = Streams.of(aadBytes);

            base64Url = base64UrlDigest;
            //guaranteed to be non-empty via the `alg` + digest check above:
            Assert.hasText(base64Url, "JWE AAD Authentication Tag cannot be null or empty.");
            digest = decode(base64Url, "JWE AAD Authentication Tag");
            if (Bytes.isEmpty(digest)) {
                String msg = "Compact JWE strings must always contain an AAD Authentication Tag.";
                throw new MalformedJwtException(msg);
            }

            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?

View on GitHub (pinned to fb71496164)

Solutions

  1. Use the recipient's PrivateKey for decryption: point keyLocator/decryptWith at the private key that pairs with the encrypting public key.
  2. Separate locators: one returning public keys for verification/encryption, one returning private keys for decryption.
  3. Check asymmetric setup — if you are the sender you should encrypt with the recipient's PublicKey, not decrypt.
  4. Ensure your key store loader returns the PrivateKey entry, not its certificate's public key.

Example fix

// before
parser.keyLocator(h -> keyPair.getPublic());
// after
parser.keyLocator(h -> keyPair.getPrivate());
Defensive patterns

Strategy: type-guard

Validate before calling

Key k = locator.locate(header);
if (k instanceof java.security.PublicKey) throw new IllegalStateException("need private key for JWE decryption");

Type guard

boolean canDecrypt(Key k) { return k instanceof PrivateKey || k instanceof SecretKey; }

Try / catch

try { parser.parse(jwe); } catch (InvalidKeyException e) { log.error("PublicKey used for decryption — check key wiring"); }

Prevention

When it happens

Trigger: Parsing/decrypting a JWE where the configured keyLocator (or key set) returns a PublicKey instance, e.g. accidentally wiring the verification/encryption key pair in the wrong direction.

Common situations: Reusing the same keyLocator for both signing-verification and decryption, loading JWKS public keys for a flow that needs your own private key, copy-pasting the sender's configuration.

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 jwtk/jjwt@fb71496164 (2026-09-09). Data as JSON: /api/errors/13e3fef59ad02a25. Report an issue: GitHub.