jwtk/jjwt · error · IllegalArgumentException

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

Thrown as IllegalArgumentException from DefaultJwtParserBuilder.verifyWith(Key) when a PrivateKey is supplied for JWS signature verification. Using a private key to verify would be both semantically wrong (private keys sign; public keys verify) and a security anti-pattern (it would expose the private key to verification logic).

Source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java:279

        }
        String msg = "JWS verification key must be either a SecretKey (for MAC algorithms) or a PublicKey " +
                "(for Signature algorithms).";
        throw new InvalidKeyException(msg);
    }

    @Override
    public JwtParserBuilder verifyWith(SecretKey key) {
        return verifyWith((Key) key);
    }

    @Override
    public JwtParserBuilder verifyWith(PublicKey key) {
        return verifyWith((Key) key);
    }

    private JwtParserBuilder verifyWith(Key key) {
        if (key instanceof PrivateKey) {
            throw new IllegalArgumentException(DefaultJwtParser.PRIV_KEY_VERIFY_MSG);
        }
        this.signatureVerificationKey = Assert.notNull(key, "signature verification key cannot be null.");
        return this;
    }

    @Override
    public JwtParserBuilder decryptWith(SecretKey key) {
        return decryptWith((Key) key);
    }

    @Override
    public JwtParserBuilder decryptWith(PrivateKey key) {
        return decryptWith((Key) key);
    }

    private JwtParserBuilder decryptWith(final Key key) {
        if (key instanceof PublicKey) {
            throw new IllegalArgumentException(DefaultJwtParser.PUB_KEY_DECRYPT_MSG);

View on GitHub (pinned to fb71496164)

Solutions

  1. Load and pass the corresponding PublicKey instead of the PrivateKey.
  2. If you only have the private key file (e.g. PEM PKCS#8), extract the public key from it once and distribute/verify with that.
  3. For HMAC (symmetric) tokens use a SecretKey, not a PrivateKey, and ensure the algorithm family matches the token.
  4. Audit key-loading code so signing and verification paths use distinct, purpose-appropriate keys.

Example fix

// before
PrivateKey priv = loadFromKeystore("signing-key");
Jwts.parser().verifyWith(priv).build().parse(jwt); // IllegalArgumentException
// after
PublicKey pub = loadFromKeystoreCert("signing-key"); // derive public key
Jwts.parser().verifyWith(pub).build().parse(jwt);
Defensive patterns

Strategy: type-guard

Validate before calling

if (key instanceof java.security.PrivateKey) {
    throw new IllegalArgumentException("Refusing to configure a PrivateKey for verification");
}

Type guard

boolean isSafeVerificationKey(java.security.Key k) {
    return k instanceof javax.crypto.SecretKey || k instanceof java.security.PublicKey;
}

Try / catch

try {
    builder.verifyWith(key);
} catch (IllegalArgumentException e) {
    // PrivateKey supplied: switch to the matching PublicKey
}

Prevention

When it happens

Trigger: Calling verifyWith(privateKey) — commonly via the deprecated setSigningKey(privateKey) path — when configuring an asymmetric (RS256/ES256/etc.) parser; e.g. loading the server's own signing keystore key for verification.

Common situations: Copy-pasting signing code into verification code; a service that both signs and verifies loading the wrong keystore entry; misunderstanding asymmetric crypto and reusing the same key object on both sides.

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


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