import { readFileSync } from "node:fs";import { createHash, createPublicKey } from "node:crypto"; const publicKey = createPublicKey(readFileSync("pub.pem"));const spkiDer = publicKey.export({ type: "spki", format: "der" }); const hex = createHash("sha256").update(spkiDer).digest("hex");console.log(hex.match(/.{2}/g).join(":")); // colon-separated, à la OpenSSL // base64 form — the shape used for HTTPS public key pinning (RFC 7469)console.log(createHash("sha256").update(spkiDer).digest("base64"));import { X509Certificate, createPrivateKey } from "node:crypto"; // from a private key: derive the public half firstconst fromPriv = createPublicKey(createPrivateKey(readFileSync("key.pem"))); // from a certificate: the embedded key, not the cert fingerprintconst cert = new X509Certificate(readFileSync("cert.pem"));const certKeyDer = cert.publicKey.export({ type: "spki", format: "der" });How it works
- Hash the DER export, not the PEM: PEM adds base64, line wrapping and headers, so hashing the text produces a fingerprint nothing else reproduces.
digest("hex")with colon separation matches OpenSSL's presentation;digest("base64")matches the pin-sha256 convention.- The same three lines fingerprint any key type — RSA, Ed25519 — because SPKI is the universal container.
Gotchas
- SSH fingerprints (
SHA256:...from ssh-keygen) hash the SSH wire format, not SPKI DER — the two will never match for the same key. Compare like with like. - A certificate has two fingerprints in circulation: the certificate fingerprint (hash of the whole cert) changes on re-issue; the key fingerprint (this recipe) survives renewals with the same key.
- Fingerprints of two PEM files can differ while the keys are identical only if the container differs — re-export both as SPKI DER before concluding the keys are different.