Skip to content
ecdsa.com

PEM ⇄ DER · Node.js

How to convert a certificate and a private key between PEM and DER in Node.js

To convert a certificate between PEM and DER in Node.js, parse it with crypto.X509Certificate: .raw is the DER and toString() the PEM. For a private key, createPrivateKey reads either form and export({ type: "pkcs8", format: "der" }) writes PKCS#8 DER. This recipe converts both ways with node:crypto alone and checks the round trip byte for byte.

Tested with Node.js 24.22.0 (node:crypto, OpenSSL 3.5.8) and OpenSSL 3.5.7 (reads the DER output).

convert.mjs — certificate and private key, PEM ⇄ DER
import { X509Certificate, createPrivateKey } from "node:crypto";
// Certificate: X509Certificate parses PEM or DER; .raw is the DER, toString() the PEM
export const certPemToDer = (pem) => new X509Certificate(pem).raw;
export const certDerToPem = (der) => new X509Certificate(der).toString();
// Private key: DER has no header, so say which structure it holds (pkcs8, sec1, pkcs1)
export const keyPemToDer = (pem) => createPrivateKey(pem).export({ type: "pkcs8", format: "der" });
export const keyDerToPem = (der) =>
createPrivateKey({ key: der, format: "der", type: "pkcs8" }).export({ type: "pkcs8", format: "pem" });

How it works

  1. new X509Certificate(pemOrDer) accepts both encodings; .raw is a Buffer with the DER (563 bytes here) and toString() the PEM, identical to the original file.
  2. createPrivateKey(pem) reads a PEM key of any type; export({ type: "pkcs8", format: "der" }) returns PKCS#8 DER (138 bytes for this P-256 key), and format: "pem" the PEM.
  3. Reading DER needs format: "der" and a type ("pkcs8", "sec1" or "pkcs1"): unlike PEM, DER has no header that names its structure.
  4. The run checks the certificate's DER with OpenSSL (openssl x509 -inform DER).

Gotchas

  • createPrivateKey({ key: der, format: "der" }) without type throws ERR_INVALID_ARG_VALUE; Node.js does not guess the structure of DER bytes.
  • type: "sec1" and type: "pkcs8" give different bytes for the same EC key (121 against 138 bytes); write PKCS#8 unless the consumer asks for SEC1 (BEGIN EC PRIVATE KEY).
  • X509Certificate reads only the first certificate of a PEM bundle — the run gets CN=api.example.com from a two-certificate file and no error — so split bundles before converting them one by one.

Related recipes