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

  1. Check the key object: signing requires PrivateKey, verification requires PublicKey (key instanceof check).
  2. Reload the correct half of the key pair from your keystore/PEM.
  3. Use the matching builder APIs: signWith(privateKey) vs verifyWith(publicKey).
  4. 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

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


AI-assisted analysis of jwtk/jjwt@fb71496164 (2026-09-09). Data as JSON: /api/errors/709b65b2303b95ec. Report an issue: GitHub.