Skip to content
ecdsa.com

Public key from a certificate · WebCrypto

How to extract the public key from an X.509 certificate in WebCrypto

To extract the public key from an X.509 certificate in the browser, parse it with @peculiar/x509 — a WebCrypto-based parser — and read cert.publicKey: toString("pem") is the SubjectPublicKeyInfo PEM and export() a CryptoKey for crypto.subtle.verify. This recipe also computes the key's SHA-256 fingerprint and base64 pin, entirely client-side.

Tested against the WebCrypto API in @peculiar/x509 2.2.0 (WebCrypto in Node.js 24.22.0 (globalThis.crypto), reflect-metadata 0.2.2) — the same crypto.subtle interface browsers implement.

extract.mjs — SPKI PEM, fingerprint, pin and a CryptoKey
import "reflect-metadata"; // @peculiar/x509 2.x needs a Reflect polyfill, imported first
import * as x509 from "@peculiar/x509"; // built on WebCrypto, runs in the browser unchanged
export async function extractPublicKey(certificatePem) {
const cert = new x509.X509Certificate(certificatePem);
const spkiPem = cert.publicKey.toString("pem"); // SubjectPublicKeyInfo, "BEGIN PUBLIC KEY"
const spkiDer = cert.publicKey.rawData; // the same SPKI as an ArrayBuffer
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", spkiDer));
const fingerprint = Array.from(digest, (b) => b.toString(16).padStart(2, "0")).join(":");
const pin = btoa(String.fromCharCode(...digest));
// A CryptoKey for crypto.subtle.verify: the certificate supplies the algorithm and curve
const key = await cert.publicKey.export();
return { spkiPem, fingerprint, pin, algorithm: key.algorithm };
}

How it works

  1. new x509.X509Certificate(pem) parses the certificate; cert.publicKey.rawData is the SPKI DER and toString("pem") the BEGIN PUBLIC KEY block.
  2. crypto.subtle.digest("SHA-256", spki) gives the key fingerprint (45:ca:a7:e3:…:7b:5f) and, base64-encoded, the SPKI pin (Rcqn43J0…e18=).
  3. cert.publicKey.export() imports the key with the algorithm the certificate names (ECDSA, P-256), so you need not pass algorithm parameters yourself.
  4. The run checks the PEM against openssl pkey -in key.pem -pubout: identical, so the private key belongs to the certificate.

Gotchas

  • crypto.subtle.exportKey("raw", key) on an EC key returns the bare 65-byte point, not the SPKI; export "spki" for fingerprints, pins and PEM.
  • importKey("spki", …) itself needs the algorithm and curve; the certificate is what tells you them, which is why cert.publicKey.export() is the simpler path.
  • btoa(String.fromCharCode(...digest)) is fine for a 32-byte digest, but not for large buffers — the spread hits the argument limit.

Related recipes