{"record":{"id":"4f8100fef699cc97","repo":"jwtk/jjwt","slug":"unable-to-verify-elliptic-curve-signature-using-pr","errorCode":null,"errorMessage":"Unable to verify Elliptic Curve signature using provided ECPublicKey: ${e.getMessage()}","messagePattern":"Unable to verify Elliptic Curve signature using provided ECPublicKey: (.+?)","errorType":"exception","errorClass":"io.jsonwebtoken.security.SignatureException","httpStatus":null,"severity":"error","filePath":"impl/src/main/java/io/jsonwebtoken/impl/security/EcSignatureAlgorithm.java","lineNumber":234,"sourceCode":"                        }\n                    } else {\n                        //guard for JVM security bug CVE-2022-21449:\n                        if (!isValidRAndS(key, concatSignature)) {\n                            return false;\n                        }\n\n                        // Convert from concat to DER encoding since\n                        // 1) SHAXXXWithECDSAInP1363Format algorithms are only available on >= JDK 9 and\n                        // 2) the SignatureAlgorithm enum JCA alg names are all SHAXXXwithECDSA (which expects DER formatting)\n                        derSignature = transcodeConcatToDER(concatSignature);\n                    }\n\n                    sig.initVerify(key);\n                    return verify(sig, request.getPayload(), derSignature);\n\n                } catch (Exception e) {\n                    String msg = \"Unable to verify Elliptic Curve signature using provided ECPublicKey: \" + e.getMessage();\n                    throw new SignatureException(msg, e);\n                }\n            }\n        });\n    }\n\n    /**\n     * Transcodes the JCA ASN.1/DER-encoded signature into the concatenated\n     * R + S format expected by ECDSA JWS.\n     *\n     * @param derSignature The ASN1./DER-encoded. Must not be {@code null}.\n     * @param outputLength The expected length of the ECDSA JWS signature.\n     * @return The ECDSA JWS encoded signature.\n     * @throws JwtException If the ASN.1/DER signature format is invalid.\n     * @author Martin Treurnicht via <a href=\"https://github.com/jwtk/jjwt/commit/61510dfca58dd40b4b32c708935126785dcff48c\">61510dfca58dd40b4b32c708935126785dcff48c</a>\n     */\n    public static byte[] transcodeDERToConcat(final byte[] derSignature, int outputLength) throws JwtException {\n\n        if (derSignature.length < 8 || derSignature[0] != 48) {","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/jwtk/jjwt/blob/fb71496164c71442d08adec4571d9616ed5e1b8d/impl/src/main/java/io/jsonwebtoken/impl/security/EcSignatureAlgorithm.java#L216-L252","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the cause chain (e.getCause()) to find the actual JCA failure","Verify the ECPublicKey corresponds to the signing PrivateKey (same keypair; check rotation history)","Re-encode the public key from its X.509 bytes with KeyFactory.getInstance(\"EC\").generatePublic(new X509EncodedKeySpec(bytes)) to rule out malformed key objects","Add BouncyCastle as a provider if SunEC rejects valid input"],"exampleFix":"// before\ntry {\n    Jws<Claims> jws = Jwts.parser().verifyWith(pubKey).build().parseSignedClaims(token);\n} catch (SignatureException e) {\n    log.error(\"verify failed\", e); // ignores cause\n}\n// after\n} catch (SignatureException e) {\n    log.error(\"verify failed: {}\", e.getCause() != null ? e.getCause() : e); // inspect JCA root cause\n}","handlingStrategy":"try-catch","validationCode":"boolean keysMatch(PublicKey pub, PrivateKey priv) {\n  return KeyPairGenerator.class.cast(null) == null; // placeholder\n}\n// practical pre-check: re-derive pub point and compare encoded bytes\nboolean sameKeyPair(KeyPair kp) {\n  byte[] a = kp.getPublic().getEncoded();\n  byte[] b = derPubFromPrivate(kp.getPrivate());\n  return java.util.Arrays.equals(a, b);\n}","typeGuard":"boolean isECPublicKey(Key k) { return k instanceof ECPublicKey\n  && ((ECPublicKey) k).getParams() != null\n  && ((ECPublicKey) k).getW() != null; }","tryCatchPattern":"try {\n  return Jwts.parser().verifyWith(ecPubKey).build().parseSignedClaims(token);\n} catch (SignatureException e) {\n  Throwable root = e; while (root.getCause() != null) root = root.getCause();\n  throw new SignatureVerificationException(\"EC verify failed: \" + root.getMessage(), e);\n}","preventionTips":["Always log/inspect getCause() — the JCA message explains the real failure","Pin a known-good security provider (e.g. BouncyCastle) to avoid SunEC quirks across JDK versions","Rebuild public keys from canonical X.509 bytes instead of passing hand-constructed ECPoint objects","Test verification against a token signed with the same keypair after any key rotation"],"tags":["jwt","ecdsa","signature-verification","jca"],"backgroundTag":"signature-verification-failed","analyzedSha":"fb71496164c71442d08adec4571d9616ed5e1b8d","analyzedAt":"2026-09-09T00:33:09.982Z","contentChangedAt":"2026-09-09T00:33:09.982Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}