ecdsa.com

Key fingerprint · WebCrypto

How to compute a public key fingerprint in WebCrypto

WebCrypto computes a public key fingerprint with two awaits: exportKey yields the SPKI DER, and crypto.subtle.digest hashes it with SHA-256. The result matches OpenSSL's DER-pipe digest byte for byte — all inside the browser.

Tested against the WebCrypto API in Node.js 26 — the same crypto.subtle interface browsers implement.

Same recipe in:Node.jsPythonGoOpenSSLWebCrypto
fingerprint of a CryptoKey
const spki = await crypto.subtle.exportKey("spki", publicKey);
const digest = await crypto.subtle.digest("SHA-256", spki);
 
const hex = [...new Uint8Array(digest)]
.map((b) => b.toString(16).padStart(2, "0"))
.join(":");
console.log(hex); // 21:2c:...:9d — same value OpenSSL computes
starting from a PEM string
function pemToBytes(pem) {
const b64 = pem.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
}
 
const spkiDer = pemToBytes(publicKeyPem); // already DER — hash directly
const digest2 = await crypto.subtle.digest("SHA-256", spkiDer);

How it works

  1. exportKey("spki", …) requires an extractable public key and returns the canonical DER as an ArrayBuffer.
  2. subtle.digest accepts the buffer directly; formatting to colon-hex or base64 happens after, in plain JS.
  3. Given a PEM, skip import/export entirely: strip the armor, base64-decode, hash — the PEM body *is* the SPKI DER.
  4. Legacy tools sometimes display SHA-1 fingerprints; subtle.digest("SHA-1", spkiDer) reproduces those for comparison — fine for matching, never for security decisions.

Gotchas

  • Both exportKey and digest return promises — a missing await hands digest a Promise and throws a confusing type error two lines away from the actual mistake.
  • Hash the decoded bytes, not the base64 string: digest over TextEncoder-encoded PEM text yields a stable-looking but incompatible value.
  • For the base64 pin form, btoa(String.fromCharCode(...new Uint8Array(digest))) is fine at 32 bytes — but remember it is the digest being base64'd, not the key.

Related recipes