Skip to content
ecdsa.com

PEM ⇄ JWK · Node.js

How to convert a PEM public key to a JWK and back in Node.js

Node.js converts a PEM public key to a JWK and back with node:crypto alone: KeyObject.export({ format: "jwk" }) writes the JWK, and createPublicKey({ key: jwk, format: "jwk" }) reads it back for export as SPKI PEM. It works for EC, RSA and Ed25519 keys; the run checks that the RFC 7515 key survives the round trip byte for byte.

Tested with Node.js 24.22.0 (node:crypto KeyObject.export({ format: "jwk" })).

to-jwk.mjs — PEM to JWK for P-256 and Ed25519 keys
import { readFileSync, writeFileSync } from "node:fs";
import { createPublicKey } from "node:crypto";
// PEM -> JWK: any public key type; the curve and key type come from the PEM
const jwk = createPublicKey(readFileSync("pub.pem")).export({ format: "jwk" });
writeFileSync("out.jwk.json", JSON.stringify(jwk));
console.log(JSON.stringify(jwk));
console.log(JSON.stringify(createPublicKey(readFileSync("ed25519.pem")).export({ format: "jwk" })));
to-pem.mjs — JWK back to an SPKI PEM
import { readFileSync, writeFileSync } from "node:fs";
import { createPublicKey } from "node:crypto";
// JWK -> PEM: a public JWK becomes an SPKI "BEGIN PUBLIC KEY" block
const jwk = JSON.parse(readFileSync("pub.jwk.json", "utf8"));
const pem = createPublicKey({ key: jwk, format: "jwk" }).export({ type: "spki", format: "pem" });
writeFileSync("out.pem", pem);
process.stdout.write(pem);

How it works

  1. createPublicKey(pem) reads BEGIN PUBLIC KEY (and also certificates, from which it takes the key).
  2. export({ format: "jwk" }) returns a plain object: kty, crv, x, y for EC; kty, crv, x for Ed25519; kty, n, e for RSA.
  3. createPublicKey({ key: jwk, format: "jwk" }) reads the JWK and validates it — an EC point that is not on the curve throws.
  4. export({ type: "spki", format: "pem" }) writes the standard PEM with 64-character lines and a final newline, so the round trip reproduces the original file exactly.

Gotchas

  • Exporting a private KeyObject as JWK includes d, the private key. Call createPublicKey(privateKey) first when the JWK is meant for publishing.
  • createPublicKey also accepts a private JWK and quietly returns its public half, so code that should only ever see public keys does not notice private material being passed in.
  • An invalid point fails with ERR_CRYPTO_INVALID_JWK: Invalid JWK EC key, the same message as a missing member — check x and y against the issuer's published values.
  • The JWK has no kid, use or alg: add them before publishing it in a JWKS (kid is often the RFC 7638 thumbprint).

Related recipes