ecdsa.com

Generate a key pair · WebCrypto

How to generate an ECDSA key pair in WebCrypto

crypto.subtle.generateKey creates an ECDSA key pair right in the browser — nothing leaves the page unless you export it. The recipe below generates a P-256 pair and serializes both halves to standard PEM so any other stack can use them.

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

Same recipe in:Node.jsPythonGoOpenSSLWebCrypto
generate a P-256 pair and export as PEM
const keyPair = await crypto.subtle.generateKey(
{ name: "ECDSA", namedCurve: "P-256" },
true, // extractable — required for exportKey below
["sign", "verify"],
);
 
const pkcs8 = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
const spki = await crypto.subtle.exportKey("spki", keyPair.publicKey);
 
const toPem = (buf, label) => {
const b64 = btoa(String.fromCharCode(...new Uint8Array(buf)));
const lines = b64.match(/.{1,64}/g).join("\n");
return `-----BEGIN ${label}-----\n${lines}\n-----END ${label}-----\n`;
};
 
console.log(toPem(pkcs8, "PRIVATE KEY"));
console.log(toPem(spki, "PUBLIC KEY"));
JWK export — often the more convenient interchange
const jwk = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
// { kty: "EC", crv: "P-256", d: "...", x: "...", y: "..." }

How it works

  1. generateKey takes the curve, the extractability flag and the allowed usages — the pair comes back as live CryptoKey objects.
  2. exportKey("pkcs8") and exportKey("spki") return DER as ArrayBuffers; base64 plus header lines turns them into the PEMs other tools read.
  3. PEM base64 is wrapped at 64 characters per line — some strict parsers actually check.

Gotchas

  • extractable: false locks the private key into the browser: signing works, exportKey throws. Great for session keys, wrong for keys you need to back up — decide before generating.
  • Only P-256, P-384 and P-521 are available. No secp256k1, no Brainpool — for those, generate elsewhere or use a pure-JS library.
  • Keys generated here are ephemeral: reloading the page loses them unless you export or store the CryptoKey in IndexedDB (which can persist even non-extractable keys).

Related recipes