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

Invalid ECDSA signature format.

Error message

Invalid ECDSA signature format.

What it means

Thrown by EcSignatureAlgorithm.transcodeConcatToDER when converting a JWS concat-encoded (R||S) ECDSA signature to DER for the JCA verifier fails for any reason. It wraps every exception from concatToDER (including ArrayIndexOutOfBoundsException) into a SignatureException, a deliberate CVE-2022-21449 (psychic signature) guard so that malformed signature bytes can never bypass verification. It means the JWT's signature bytes were not a plausible fixed-length R||S pair.

Source

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

        System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i);
        System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j);

        return concatSignature;
    }

    /**
     * Transcodes the ECDSA JWS signature into ASN.1/DER format for use by the JCA verifier.
     *
     * @param jwsSignature The JWS signature, consisting of the concatenated R and S values. Must not be {@code null}.
     * @return The ASN.1/DER encoded signature.
     * @throws JwtException If the ECDSA JWS signature format is invalid.
     */
    public static byte[] transcodeConcatToDER(byte[] jwsSignature) throws JwtException {
        try {
            return concatToDER(jwsSignature);
        } catch (Exception e) { // CVE-2022-21449 guard
            String msg = "Invalid ECDSA signature format.";
            throw new SignatureException(msg, e);
        }
    }

    /**
     * Converts the specified concat-encoded signature to a DER-encoded signature.
     *
     * @param jwsSignature concat-encoded signature
     * @return correpsonding DER-encoded signature
     * @throws ArrayIndexOutOfBoundsException if the signature cannot be converted
     * @author Martin Treurnicht via <a href="https://github.com/jwtk/jjwt/commit/61510dfca58dd40b4b32c708935126785dcff48c">61510dfca58dd40b4b32c708935126785dcff48c</a>
     */
    private static byte[] concatToDER(byte[] jwsSignature) throws ArrayIndexOutOfBoundsException {

        int rawLen = jwsSignature.length / 2;

        int i = rawLen;

        while ((i > 0) && (jwsSignature[rawLen - i] == 0)) {

View on GitHub (pinned to fb71496164)

Solutions

  1. Check the token is complete and unmodified: base64url-decode the signature part and confirm its length is exactly 2x the curve size (64 bytes for ES256)
  2. Regenerate the token with Jwts.builder().signWith(ecKey, Jwts.SIG.ES256...) — do not hand-assemble signatures
  3. Verify the key and alg match: an EC public key with ES256, not RS256/HS256 tokens verified with the wrong parser config
  4. Catch SignatureException (a JwtException subclass) around parsing and treat the token as untrusted/reject it

Example fix

// before: assuming any token parses
Claims c = Jwts.parser().verifyWith(pubKey).build()
    .parseSignedClaims(token).getPayload();
// after: reject malformed signatures explicitly
try {
    Claims c = Jwts.parser().verifyWith(pubKey).build()
        .parseSignedClaims(token).getPayload();
} catch (SignatureException e) {
    throw new UntrustedTokenException("malformed ECDSA signature", e);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check concat signature length matches curve before verify
int half = 32; // ES256
byte[] sig = Base64.getUrlDecoder().decode(sigPart);
if (sig.length != 2 * half) throw new IllegalArgumentException("bad ECDSA signature length");

Type guard

boolean isValidConcatSignature(byte[] sig, int halfLen) {
    return sig != null && sig.length == 2 * halfLen && (sig[0] != 0 || sig[halfLen] != 0);
}

Try / catch

try {
    return Jwts.parser().verifyWith(ecPub).build().parseSignedClaims(token).getPayload();
} catch (SignatureException e) {
    audit.warn("Rejected token with malformed ECDSA signature");
    throw new UntrustedTokenException(e);
}

Prevention

When it happens

Trigger: Verifying an ES256/ES384/ES512 JWS whose decoded signature is null/empty, has odd or zero length, is shorter than 2*rawLen bytes (so R or S extraction indexes out of bounds), or is otherwise malformed — typically via Jwts.parser().verifyWith(ecPublicKey).parseSignedClaims(token).

Common situations: Truncated or corrupted JWTs in transit; attackers sending junk signature bytes (the guard exists precisely for this); tokens minted by code that base64-encodes the wrong byte range; tests feeding arbitrary byte arrays to the verify path; wrong algorithm configured so a non-EC signature reaches the EC verifier.

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/022788b207e803fe. Report an issue: GitHub.