{"record":{"id":"13e3fef59ad02a25","repo":"jwtk/jjwt","slug":"publickeys-may-not-be-used-to-decrypt-data-public","errorCode":null,"errorMessage":"PublicKeys may not be used to decrypt data. PublicKeys are used to encrypt, and PrivateKeys are used to decrypt.","messagePattern":"PublicKeys may not be used to decrypt data\\. PublicKeys are used to encrypt, and PrivateKeys are used to decrypt\\.","errorType":"exception","errorClass":"InvalidKeyException","httpStatus":null,"severity":"error","filePath":"impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java","lineNumber":546,"sourceCode":"            buf.get(aadBytes);\n            InputStream aad = Streams.of(aadBytes);\n\n            base64Url = base64UrlDigest;\n            //guaranteed to be non-empty via the `alg` + digest check above:\n            Assert.hasText(base64Url, \"JWE AAD Authentication Tag cannot be null or empty.\");\n            digest = decode(base64Url, \"JWE AAD Authentication Tag\");\n            if (Bytes.isEmpty(digest)) {\n                String msg = \"Compact JWE strings must always contain an AAD Authentication Tag.\";\n                throw new MalformedJwtException(msg);\n            }\n\n            Key key = this.keyLocator.locate(jweHeader);\n            if (key == null) {\n                String msg = \"Cannot decrypt JWE payload: unable to locate key for JWE with header: \" + jweHeader;\n                throw new UnsupportedJwtException(msg);\n            }\n            if (key instanceof PublicKey) {\n                throw new InvalidKeyException(PUB_KEY_DECRYPT_MSG);\n            }\n\n            // extract key-specific provider if necessary;\n            Provider provider = ProviderKey.getProvider(key, this.provider);\n            key = ProviderKey.getKey(key); // this must be called after ProviderKey.getProvider\n            DecryptionKeyRequest<Key> request =\n                    new DefaultDecryptionKeyRequest<>(cekBytes, provider, null, jweHeader, encAlg, key);\n            final SecretKey cek = keyAlg.getDecryptionKey(request);\n            if (cek == null) {\n                String msg = \"The '\" + keyAlg.getId() + \"' JWE key algorithm did not return a decryption key. \" +\n                        \"Unable to perform '\" + encAlg.getId() + \"' decryption.\";\n                throw new IllegalStateException(msg);\n            }\n\n            // During decryption, the available Provider applies to the KeyAlgorithm, not the AeadAlgorithm, mostly\n            // because all JVMs support the standard AeadAlgorithms (especially with BouncyCastle in the classpath).\n            // As such, the provider here is intentionally omitted (null):\n            // TODO: add encProvider(Provider) builder method that applies to this request only?","sourceCodeStart":528,"sourceCodeEnd":564,"githubUrl":"https://github.com/jwtk/jjwt/blob/fb71496164c71442d08adec4571d9616ed5e1b8d/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java#L528-L564","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use the recipient's PrivateKey for decryption: point keyLocator/decryptWith at the private key that pairs with the encrypting public key.","Separate locators: one returning public keys for verification/encryption, one returning private keys for decryption.","Check asymmetric setup — if you are the sender you should encrypt with the recipient's PublicKey, not decrypt.","Ensure your key store loader returns the PrivateKey entry, not its certificate's public key."],"exampleFix":"// before\nparser.keyLocator(h -> keyPair.getPublic());\n// after\nparser.keyLocator(h -> keyPair.getPrivate());","handlingStrategy":"type-guard","validationCode":"Key k = locator.locate(header);\nif (k instanceof java.security.PublicKey) throw new IllegalStateException(\"need private key for JWE decryption\");","typeGuard":"boolean canDecrypt(Key k) { return k instanceof PrivateKey || k instanceof SecretKey; }","tryCatchPattern":"try { parser.parse(jwe); } catch (InvalidKeyException e) { log.error(\"PublicKey used for decryption — check key wiring\"); }","preventionTips":["Keep separate locators/configs for encrypt (PublicKey) and decrypt (PrivateKey) flows.","Name your key configuration explicitly (e.g. recipientPrivateKey) to avoid copy-paste mixups.","When loading from keystores, fetch PrivateKey entries, not certificate public keys.","Add startup assertions that decryption keys are PrivateKey/SecretKey instances."],"tags":["jwe","invalid-key","public-key","asymmetric-crypto"],"backgroundTag":"invalid-argument-value","analyzedSha":"fb71496164c71442d08adec4571d9616ed5e1b8d","analyzedAt":"2026-09-09T00:33:09.982Z","contentChangedAt":"2026-09-09T00:33:09.982Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}