jwtk/jjwt · error · InvalidKeyException
${getId()} ${keyType(signing)} keys must be ${type.getSimple
Error message
${getId()} ${keyType(signing)} keys must be ${type.getSimpleName()}s (implement ${type.getName()}). Provided key type: ${key.getClass().getName()}. What it means
validateKey enforces that signing uses a PrivateKey and verification uses a PublicKey for the configured signature algorithm. A key of the wrong type throws InvalidKeyException naming the required interface and the actual key class.
Source
Thrown at impl/src/main/java/io/jsonwebtoken/impl/security/AbstractSignatureAlgorithm.java:48
abstract class AbstractSignatureAlgorithm extends AbstractSecureDigestAlgorithm<PrivateKey, PublicKey>
implements SignatureAlgorithm {
private static final String KEY_TYPE_MSG_PATTERN =
"{0} {1} keys must be {2}s (implement {3}). Provided key type: {4}.";
AbstractSignatureAlgorithm(String id, String jcaName) {
super(id, jcaName);
}
@Override
protected void validateKey(Key key, boolean signing) {
// https://github.com/jwtk/jjwt/issues/68:
Class<?> type = signing ? PrivateKey.class : PublicKey.class;
if (!type.isInstance(key)) {
String msg = MessageFormat.format(KEY_TYPE_MSG_PATTERN, getId(),
keyType(signing), type.getSimpleName(), type.getName(), key.getClass().getName());
throw new InvalidKeyException(msg);
}
}
protected final byte[] sign(Signature sig, InputStream payload) throws Exception {
byte[] buf = new byte[2048];
int len = 0;
while (len != -1) {
len = payload.read(buf);
if (len > 0) sig.update(buf, 0, len);
}
return sig.sign();
}
@Override
protected byte[] doDigest(final SecureRequest<InputStream, PrivateKey> request) {
return jca(request).withSignature(new CheckedFunction<Signature, byte[]>() {
@Override
public byte[] apply(Signature sig) throws Exception {View on GitHub (pinned to fb71496164)
Solutions
- Check the key object: signing requires PrivateKey, verification requires PublicKey (key instanceof check).
- Reload the correct half of the key pair from your keystore/PEM.
- Use the matching builder APIs: signWith(privateKey) vs verifyWith(publicKey).
- For symmetric use cases, use HMAC algorithms (HS256/384/512) with SecretKey instead.
Example fix
// before alg.verify(secureRequestWithPrivateKey); // after PublicKey pub = keyPair.getPublic(); alg.verify(secureRequest(pub));
Defensive patterns
Strategy: validation
Validate before calling
if (signing && !(key instanceof PrivateKey)) throw new InvalidKeyException("Signing requires a PrivateKey");
if (!signing && !(key instanceof PublicKey)) throw new InvalidKeyException("Verification requires a PublicKey"); Type guard
boolean isCorrectHalf(Key k, boolean signing) { return signing ? k instanceof PrivateKey : k instanceof PublicKey; } Try / catch
try { alg.verify(req); }
catch (InvalidKeyException e) { log.error("Wrong key half: {}", e.getMessage()); reloadKeys(); } Prevention
- Name key variables explicitly (signingKey/verifyingKey).
- Check instanceof PrivateKey before sign calls.
- Keep public/private PEM files in clearly named files.
When it happens
Trigger: Calling jwt.signWith(privateKeyOfWrongType) or a SignatureAlgorithm instance's verify with a PrivateKey, or passing a symmetric SecretKey where an asymmetric key is required.
Common situations: Loading the public cert/key when the private key was intended; storing keys in a Map and picking the wrong entry; mixing PEM public/private files; using a SecretKey (HMAC) with an RSA algorithm id.
Related errors
- Unable to create ${type.getSimpleName()} from JWK ${ctx}: ${
- Unable to verify ${getId()} signature with JCA algorithm '${
- The ${keyType(signing)} key's algorithm cannot be null or em
- Invalid RSA key algorithm name.
- RSASSA-PSS keys may not be used for ${keyType}, only digital
AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09).
Data as JSON: /api/errors/709b65b2303b95ec.
Report an issue: GitHub.