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

Provided signature is ${actual} but ${id} signatures must be

Error message

Provided signature is ${actual} but ${id} signatures must be exactly ${expected} per [RFC 7518, Section 3.4 (validation)](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4).

What it means

Per RFC 7518 Section 3.4, a JWS ECDSA signature must be the raw R||S concatenation of exactly 2*fieldSize bytes (64 for ES256, 96 for ES384, 132 for ES512). During verification the provided signature had a different byte length, so a SignatureException is thrown before parsing R and S. This typically means the signature was not produced in the JWS (concatenated) format.

Source

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

                    // mandated per https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4 :
                    if (signatureByteLength != concatSignature.length) {
                        /*
                         * If the expected size is not valid for JOSE, fall back to ASN.1 DER signature IFF the application
                         * is configured to do so.  This fallback is for backwards compatibility ONLY (to support tokens
                         * generated by early versions of jjwt) and backwards compatibility will be removed in a future
                         * version of this library.  This fallback is only enabled if the system property is set to 'true' due to
                         * the risk of CVE-2022-21449 attacks on early JVM versions 15, 17 and 18.
                         */
                        // TODO: remove for 1.0 (DER-encoding support is not in the JWT RFCs)
                        if (concatSignature[0] == 0x30 &&
                                "true".equalsIgnoreCase(System.getProperty(DER_ENCODING_SYS_PROPERTY_NAME))) {
                            derSignature = concatSignature;
                        } else {
                            String msg = "Provided signature is " + Bytes.bytesMsg(concatSignature.length) + " but " +
                                    getId() + " signatures must be exactly " + Bytes.bytesMsg(signatureByteLength) +
                                    " per [RFC 7518, Section 3.4 (validation)]" +
                                    "(https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4).";
                            throw new SignatureException(msg);
                        }
                    } 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();

View on GitHub (pinned to fb71496164)

Solutions

  1. Ensure the signature is the RFC 7518 raw R||S concat format, exactly 64/96/132 bytes depending on algorithm
  2. If the source emits DER, transcode with EllipticCurveProvider.transcodeDerToConcat (or JJWT'stranscodeDERToConcat path) before verifying
  3. Re-verify the base64url signature segment for truncation/corruption (check decoded length with Base64.getUrlDecoder())

Example fix

// before
byte[] sig = derSignatureFromOtherLib; // ASN.1 DER, wrong length
parser.verifyWith(pubKey).parseSignedClaims(jwt); // fails
// after
byte[] concat = io.jsonwebtoken.impl.security.EcSignatureAlgorithm.transcodeDERToConcat(derSignature, 64);
// or better: only verify signatures produced in JWS concat format by the signing side
Defensive patterns

Strategy: validation

Validate before calling

boolean isJwsConcatSignature(byte[] sig, int fieldSize) {
  return sig != null && sig.length == fieldSize * 2; // 64 / 96 / 132
}

Try / catch

try {
  Jws<Claims> jws = Jwts.parser().verifyWith(pubKey).build().parseSignedClaims(token);
} catch (SignatureException e) {
  if (e.getMessage().contains("must be exactly")) {
    throw new IllegalArgumentException("Signature not in RFC 7518 concat format", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing a DER/ASN.1-encoded ECDSA signature (as output by java.security.Signature.sign() or OpenSSL by default) directly into the JWT, or passing a truncated/corrupted signature string to a parser's setSigningKey/verifyWith flow.

Common situations: Verifying a JWT signature produced by another library (node-jose, OpenSSL dgst) that emits DER format; hand-splitting base64url JWT parts and passing a damaged signature; storing signatures in a DB column that truncates bytes.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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