Error message
TypeError: "ES256" signatures must be "64" bytes, saw "71"
Also printed as
- "ES256" signatures must be "64" bytes, saw "70"
- "ES256" signatures must be "64" bytes, saw "72"
- "ES384" signatures must be "96" bytes, saw "103"
- "ES512" signatures must be "132" bytes, saw "139"
The "saw" number varies between 70 and 72 for ES256 (the DER length depends on the leading bits of r and s); ES384 expects 96 bytes and ES512 132. The error is a plain `TypeError`, not a `JsonWebTokenError`, so `instanceof` checks for jsonwebtoken errors do not catch it.
jsonwebtoken / jws / jwa (Node.js) — TypeError from ecdsa-sig-formatter
What it means
The token's signature segment decodes to 71 bytes, but an ES256 JWS signature must be exactly 64 bytes: the raw concatenation r‖s defined in RFC 7518 §3.4. A 70–72 byte value that starts with `0x30` is the ASN.1 DER encoding of the same signature — the format OpenSSL, Java, KMS services and Node's `crypto.sign()` produce by default. The token was minted by a signer that skipped the DER-to-raw conversion; jsonwebtoken (through ecdsa-sig-formatter) rejects it before any verification.
Why it happens
A hand-rolled signer emitted DER
commonCode that builds the JWT itself and signs with `crypto.sign("sha256", data, key)` or `createSign().sign(key)` gets DER output unless it passes `dsaEncoding: "ieee-p1363"`. The token looks fine everywhere until a standards-compliant verifier checks the length.
A KMS or HSM signed the token
commonAWS KMS `Sign` and Google Cloud KMS `asymmetricSign` return ECDSA signatures in DER, so JWT signing on top of them needs an explicit conversion step. (Azure Key Vault and PKCS#11 `CKM_ECDSA` already return raw r‖s — check what your provider documents before converting.)
Another language's default encoding
occasionalJava's `SHA256withECDSA`, Python cryptography's `sign()`, Go's `ecdsa.SignASN1` and the OpenSSL CLI all produce DER. Libraries made for JOSE convert automatically; generic crypto calls do not.
The signature segment is corrupted
rareIf the reported length is not in the 70–72 range (for example "saw 2"), the third segment was truncated or replaced, not encoded differently.
How to fix it
- 1.
Ask node:crypto for raw r‖s
`dsaEncoding: "ieee-p1363"` makes Node emit the fixed-length format JWS requires. Better still, sign with a JOSE library (jsonwebtoken's `jwt.sign`, jose's `SignJWT`), which does this for you.
js import { sign } from "node:crypto"; const signature = sign("sha256", Buffer.from(signingInput), { key: privateKey, dsaEncoding: "ieee-p1363", // raw r||s: 64 bytes for P-256, as RFC 7518 §3.4 requires }); const token = signingInput + "." + signature.toString("base64url"); - 2.
Convert DER from a KMS before assembling the token
`ecdsa-sig-formatter` (installed with jsonwebtoken through jws/jwa) converts DER to the JOSE format. The DER ⇄ raw converter shows r and s for both forms so you can confirm the conversion.
js import ecdsaSigFormatter from "ecdsa-sig-formatter"; // AWS KMS and Google Cloud KMS return DER; convert once before assembling the JWS. const derSignature = Buffer.from(kmsResponse.Signature); const joseSignature = ecdsaSigFormatter.derToJose(derSignature, "ES256"); // base64url, 64 bytes decoded const token = signingInput + "." + joseSignature;