jwtk/jjwt · error · io.jsonwebtoken.security.SignatureException

Unable to verify Elliptic Curve signature using provided…

Error message

Unable to verify Elliptic Curve signature using provided ECPublicKey: ${e.getMessage()}

What it means

After JJWT has validated key size and signature format, it delegates to the JDK's java.security.Signature to verify the DER-encoded signature against the ECPublicKey. Any exception thrown inside that JCA verification step is wrapped and rethrown as a SignatureException with the underlying JCA message. The root cause is in the wrapped exception (getCause()), not in JJWT itself.

Solutions

  1. Inspect the cause chain (e.getCause()) to find the actual JCA failure
  2. Verify the ECPublicKey corresponds to the signing PrivateKey (same keypair; check rotation history)
  3. Re-encode the public key from its X.509 bytes with KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(bytes)) to rule out malformed key objects
  4. Add BouncyCastle as a provider if SunEC rejects valid input

Example fix

// before
try {
    Jws<Claims> jws = Jwts.parser().verifyWith(pubKey).build().parseSignedClaims(token);
} catch (SignatureException e) {
    log.error("verify failed", e); // ignores cause
}
// after
} catch (SignatureException e) {
    log.error("verify failed: {}", e.getCause() != null ? e.getCause() : e); // inspect JCA root cause
}
Defensive patterns

Strategy: try-catch

Validate before calling

boolean keysMatch(PublicKey pub, PrivateKey priv) {
  return KeyPairGenerator.class.cast(null) == null; // placeholder
}
// practical pre-check: re-derive pub point and compare encoded bytes
boolean sameKeyPair(KeyPair kp) {
  byte[] a = kp.getPublic().getEncoded();
  byte[] b = derPubFromPrivate(kp.getPrivate());
  return java.util.Arrays.equals(a, b);
}

Type guard

boolean isECPublicKey(Key k) { return k instanceof ECPublicKey
  && ((ECPublicKey) k).getParams() != null
  && ((ECPublicKey) k).getW() != null; }

Try / catch

try {
  return Jwts.parser().verifyWith(ecPubKey).build().parseSignedClaims(token);
} catch (SignatureException e) {
  Throwable root = e; while (root.getCause() != null) root = root.getCause();
  throw new SignatureVerificationException("EC verify failed: " + root.getMessage(), e);
}

Prevention

When it happens

Trigger: initVerify or verify() failing inside the JCA provider — e.g. invalid key encoding, provider-specific errors, or signature bytes rejected mid-verification — when calling parseSignedClaims/parse on an ES* signed JWT.

Common situations: Using a public key reconstructed from wrong EC point/params; JDK provider quirks (SunEC vs BouncyCastle); corrupted signature bytes that fail DER conversion inside the JDK; mismatched key from a different keypair after key rotation.

Related errors


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

Appendix: source

Thrown at impl/src/main/java/io/jsonwebtoken/impl/security/EcSignatureAlgorithm.java:234

                        }
                    } else {
                        //guard for JVM security bug CVE-2022-21449:
                        if (!isValidRAndS(key, concatSignature)) {
                            return false;
                        }

                        // Convert from concat to DER encoding since
                        // 1) SHAXXXWithECDSAInP1363Format algorithms are only available on >= JDK 9 and
                        // 2) the SignatureAlgorithm enum JCA alg names are all SHAXXXwithECDSA (which expects DER formatting)
                        derSignature = transcodeConcatToDER(concatSignature);
                    }

                    sig.initVerify(key);
                    return verify(sig, request.getPayload(), derSignature);

                } catch (Exception e) {
                    String msg = "Unable to verify Elliptic Curve signature using provided ECPublicKey: " + e.getMessage();
                    throw new SignatureException(msg, e);
                }
            }
        });
    }

    /**
     * Transcodes the JCA ASN.1/DER-encoded signature into the concatenated
     * R + S format expected by ECDSA JWS.
     *
     * @param derSignature The ASN1./DER-encoded. Must not be {@code null}.
     * @param outputLength The expected length of the ECDSA JWS signature.
     * @return The ECDSA JWS encoded signature.
     * @throws JwtException If the ASN.1/DER signature format is invalid.
     * @author Martin Treurnicht via <a href="https://github.com/jwtk/jjwt/commit/61510dfca58dd40b4b32c708935126785dcff48c">61510dfca58dd40b4b32c708935126785dcff48c</a>
     */
    public static byte[] transcodeDERToConcat(final byte[] derSignature, int outputLength) throws JwtException {

        if (derSignature.length < 8 || derSignature[0] != 48) {

View on GitHub (pinned to fb71496164)