{"record":{"id":"8552485ccf472461","repo":"jwtk/jjwt","slug":"privatekeys-may-not-be-used-to-verify-digital-sign","errorCode":null,"errorMessage":"PrivateKeys may not be used to verify digital signatures. PrivateKeys are used to sign, and PublicKeys are used to verify.","messagePattern":"PrivateKeys may not be used to verify digital signatures\\. PrivateKeys are used to sign, and PublicKeys are used to verify\\.","errorType":"exception","errorClass":"InvalidKeyException","httpStatus":null,"severity":"error","filePath":"impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java","lineNumber":299,"sourceCode":"        }\n        Assert.stateNotNull(algorithm, \"JWS Signature Algorithm cannot be null.\");\n\n        //digitally signed, let's assert the signature:\n        Key key;\n        if (claims != null) {\n            key = resolver.resolveSigningKey(jwsHeader, claims);\n        } else {\n            key = resolver.resolveSigningKey(jwsHeader, payload.getBytes());\n        }\n        if (key == null) {\n            String msg = \"Cannot verify JWS signature: unable to locate signature verification key for JWS with header: \" + jwsHeader;\n            throw new UnsupportedJwtException(msg);\n        }\n        Provider provider = ProviderKey.getProvider(key, this.provider); // extract if necessary\n        key = ProviderKey.getKey(key); // unwrap if necessary, MUST be called after ProviderKey.getProvider\n        Assert.stateNotNull(key, \"ProviderKey cannot be null.\"); //ProviderKey impl doesn't allow null\n        if (key instanceof PrivateKey) {\n            throw new InvalidKeyException(PRIV_KEY_VERIFY_MSG);\n        }\n\n        final byte[] signature = decode(tokenized.getDigest(), \"JWS signature\");\n\n        //re-create the jwt part without the signature.  This is what is needed for signature verification:\n        InputStream payloadStream = null;\n        InputStream verificationInput;\n        if (jwsHeader.isPayloadEncoded()) {\n            int len = tokenized.getProtected().length() + 1 + tokenized.getPayload().length();\n            CharBuffer cb = CharBuffer.allocate(len);\n            cb.put(Strings.wrap(tokenized.getProtected()));\n            cb.put(SEPARATOR_CHAR);\n            cb.put(Strings.wrap(tokenized.getPayload()));\n            cb.rewind();\n            ByteBuffer bb = StandardCharsets.US_ASCII.encode(cb);\n            bb.rewind();\n            byte[] data = new byte[bb.remaining()];\n            bb.get(data);","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/jwtk/jjwt/blob/fb71496164c71442d08adec4571d9616ed5e1b8d/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java#L281-L317","documentation":"The parser resolves the signature verification key and, after unwrapping Provider wrappers, checks that it is not a PrivateKey. Private keys are for signing only; verification must use the corresponding PublicKey, so a PrivateKey is rejected with InvalidKeyException.","triggerScenarios":"Passing a PrivateKey to parser.verifyWith(key) (or returning one from a SigningKeyResolver) and then parsing a signed JWT.","commonSituations":"Copy-pasting the signing key configuration into the parser config; loading the wrong key from a keystore (getKey instead of getCertificate().getPublicKey()); symmetric code where the same Key variable is used for both sign and verify paths.","solutions":["Pass the corresponding PublicKey (e.g. certificate.getPublicKey() or keyPair.getPublic()) to verifyWith(...)","In SigningKeyResolver implementations, ensure only PublicKeys are returned for verification","Fix keystore loading code to retrieve the public certificate rather than the private key entry"],"exampleFix":"// before\nJwsParser p = Jwts.parser().verifyWith(privateKey).build();\n// after\nPublicKey pub = keyPair.getPublic();\nJwsParser p = Jwts.parser().verifyWith(pub).build();","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"boolean isVerificationKey(Key k) {\n    return !(k instanceof PrivateKey) && k instanceof PublicKey || k instanceof SecretKey;\n}","tryCatchPattern":"try {\n    return parser.parseSignedClaims(token);\n} catch (InvalidKeyException e) {\n    log.error(\"Verification key must be public/secret, not private\");\n    throw new UnauthorizedException(e);\n}","preventionTips":["Separate signing and verification configuration objects so keys cannot be swapped","When loading from keystores, fetch certificates/PublicKey entries for verify paths","Add unit tests asserting parser config rejects private keys"],"tags":["jwt","signature","invalid-key"],"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"}