import { readFileSync } from "node:fs";import { createPublicKey, verify } from "node:crypto"; const publicKey = createPublicKey(readFileSync("pub.pem")); // SPKI PEMconst 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");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
createPublicKeyaccepts an SPKI PEM (-----BEGIN PUBLIC KEY-----) directly; DER and JWK work too via an options object.verify("sha256", …)hashes the message for you — pass the original message bytes, never a pre-computed digest.- The default signature format is ASN.1 DER. For raw
r‖ssignatures, wrap the key as{ key, dsaEncoding: "ieee-p1363" }. verifyreturns 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-p1363is set. Checksignature.lengthfirst: 64/96/132 means raw, ~70 starting with0x30means DER. - The hash must match the signer exactly — verifying a SHA-384 signature with
"sha256"yieldsfalsewith 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.