jwtk/jjwt · error · InvalidKeyException
PrivateKeys may not be used to verify digital signatures. Pr
Error message
PrivateKeys may not be used to verify digital signatures. PrivateKeys are used to sign, and PublicKeys are used to verify.
What it means
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.
Source
Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java:299
}
Assert.stateNotNull(algorithm, "JWS Signature Algorithm cannot be null.");
//digitally signed, let's assert the signature:
Key key;
if (claims != null) {
key = resolver.resolveSigningKey(jwsHeader, claims);
} else {
key = resolver.resolveSigningKey(jwsHeader, payload.getBytes());
}
if (key == null) {
String msg = "Cannot verify JWS signature: unable to locate signature verification key for JWS with header: " + jwsHeader;
throw new UnsupportedJwtException(msg);
}
Provider provider = ProviderKey.getProvider(key, this.provider); // extract if necessary
key = ProviderKey.getKey(key); // unwrap if necessary, MUST be called after ProviderKey.getProvider
Assert.stateNotNull(key, "ProviderKey cannot be null."); //ProviderKey impl doesn't allow null
if (key instanceof PrivateKey) {
throw new InvalidKeyException(PRIV_KEY_VERIFY_MSG);
}
final byte[] signature = decode(tokenized.getDigest(), "JWS signature");
//re-create the jwt part without the signature. This is what is needed for signature verification:
InputStream payloadStream = null;
InputStream verificationInput;
if (jwsHeader.isPayloadEncoded()) {
int len = tokenized.getProtected().length() + 1 + tokenized.getPayload().length();
CharBuffer cb = CharBuffer.allocate(len);
cb.put(Strings.wrap(tokenized.getProtected()));
cb.put(SEPARATOR_CHAR);
cb.put(Strings.wrap(tokenized.getPayload()));
cb.rewind();
ByteBuffer bb = StandardCharsets.US_ASCII.encode(cb);
bb.rewind();
byte[] data = new byte[bb.remaining()];
bb.get(data);View on GitHub (pinned to fb71496164)
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
Example fix
// before JwsParser p = Jwts.parser().verifyWith(privateKey).build(); // after PublicKey pub = keyPair.getPublic(); JwsParser p = Jwts.parser().verifyWith(pub).build();
Defensive patterns
Strategy: type-guard
Type guard
boolean isVerificationKey(Key k) {
return !(k instanceof PrivateKey) && k instanceof PublicKey || k instanceof SecretKey;
} Try / catch
try {
return parser.parseSignedClaims(token);
} catch (InvalidKeyException e) {
log.error("Verification key must be public/secret, not private");
throw new UnauthorizedException(e);
} Prevention
- 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
When it happens
Trigger: Passing a PrivateKey to parser.verifyWith(key) (or returning one from a SigningKeyResolver) and then parsing a signed JWT.
Common situations: 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.
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
- Unsupported signature algorithm '${alg}': ${e.getMessage()}
- Cannot verify JWS signature: unable to locate signature veri
- Unable to verify JWS signature: the parser has encountered a
- JWS verification key must be either a SecretKey (for MAC alg
- Unrecognized EC key algorithm name.
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/8552485ccf472461.
Report an issue: GitHub.