ecdsa.com

Verify a signature · Node.js

How to verify an ECDSA signature in Node.js

Node.js can verify an ECDSA signature with the built-in node:crypto module — no third-party packages. The one thing to get right is the signature encoding: crypto.verify expects ASN.1 DER by default and needs a single option flipped for raw 64-byte signatures.

Tested with Node.js 26 (OpenSSL 3.x backend).

Same recipe in:Node.jsPythonGoOpenSSLWebCrypto
verify.mjs — verify a DER signature (what OpenSSL produces)
import { readFileSync } from "node:fs";
import { createPublicKey, verify } from "node:crypto";
 
const publicKey = createPublicKey(readFileSync("pub.pem")); // SPKI PEM
const message = readFileSync("message.txt");
const signature = readFileSync("sig.der"); // ASN.1 DER, 70-72 bytes on P-256
 
const ok = verify("sha256", message, publicKey, signature);
console.log(ok ? "valid" : "INVALID");
raw 64-byte signatures (WebCrypto, JWS) need dsaEncoding
const okRaw = verify(
"sha256",
message,
{ key: publicKey, dsaEncoding: "ieee-p1363" },
readFileSync("sig.raw"), // r‖s, exactly 64 bytes on P-256
);
console.log(okRaw ? "valid" : "INVALID");

How it works

  1. createPublicKey accepts an SPKI PEM (-----BEGIN PUBLIC KEY-----) directly; DER and JWK work too via an options object.
  2. verify("sha256", …) hashes the message for you — pass the original message bytes, never a pre-computed digest.
  3. The default signature format is ASN.1 DER. For raw r‖s signatures, wrap the key as { key, dsaEncoding: "ieee-p1363" }.
  4. verify returns a boolean for a wrong signature; it only throws on malformed keys or unsupported parameters.

Gotchas

  • A 64-byte signature fails against the DER default, and a DER signature fails once ieee-p1363 is set. Check signature.length first: 64/96/132 means raw, ~70 starting with 0x30 means DER.
  • The hash must match the signer exactly — verifying a SHA-384 signature with "sha256" yields false with no further diagnostics.
  • If verification keeps failing with inputs you believe are right, suspect encodings before keys: base64 vs base64url of the signature, or a UTF-8 vs hex reading of the message.

Related recipes