import { generateKeyPairSync } from "node:crypto"; const { privateKey, publicKey } = generateKeyPairSync("ec", { namedCurve: "P-256", // aka prime256v1 / secp256r1}); const privPem = privateKey.export({ type: "pkcs8", format: "pem" });const pubPem = publicKey.export({ type: "spki", format: "pem" });console.log(privPem); // -----BEGIN PRIVATE KEY-----console.log(pubPem); // -----BEGIN PUBLIC KEY-----console.log(privateKey.export({ format: "jwk" }));// { kty: 'EC', crv: 'P-256', d: '...', x: '...', y: '...' }How it works
generateKeyPairSync("ec", { namedCurve })returns liveKeyObjects — usable for signing immediately, no export required.type: "pkcs8"gives the modernBEGIN PRIVATE KEYcontainer;type: "sec1"exists for the legacyBEGIN EC PRIVATE KEYlayout.type: "spki"is the universal public key container that every stack on this site accepts.- There is also a callback-based
generateKeyPairfor async contexts — same options.
Gotchas
namedCurveaccepts both naming families:"P-256"and"prime256v1"are the same curve. Node also supports"secp256k1"— WebCrypto in the browser does not, so don't generate secp256k1 keys destined for it.- The exported private PEM is unencrypted. Either pass
cipher/passphrasetoexport, or make sure the file lands with0600permissions outside any repository. - P-256 signs with SHA-256 by convention; if you pick P-384, plan for SHA-384 everywhere — mixed pairings verify fine within one stack and then surprise the next one.