mrkeyoor.com_
Sun 20 Sept 06:59 UTC
npmSecurityupdated 20 Sept 2026

node-forge review

node-forge implements cryptographic primitives and file formats in JavaScript: RSA, Ed25519, AES, hashes, HMAC, PBKDF2, ASN.1, X.509, CSRs, PKCS#7, PKCS#8, and PKCS#12. Its useful niche is certificate and container work that Node crypto and browser WebCrypto do not expose. Version 1.4.0 is primarily a security release fixing four high-severity flaws in big-integer inversion, RSA verification, Ed25519 verification, and certificate-chain checks. Our full browser import was 279.9 KB minified and 73.9 KB gzipped.

27.6Mdownloads / wk
Verdict

Install node-forge for X.509, CSR, ASN.1, or PKCS container work that platform crypto does not cover. Pin 1.4.0 or newer, convert byte formats explicitly, and use node:crypto or WebCrypto for ordinary primitives.

We installed it

Lab card: what happened when we installed node-forgeScreenshot of node-forge documentation
Install✓ · 0.4s1 package on disk · 2 MB
ImportESM import works · require() works · CommonJS package
Browser73.9 KBgzipped (279.9 KB minified), bundled with esbuild
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does node-forge install cleanly?

Yes. In a fresh container with an empty cache, npm install node-forge finished in 0.4s, leaving 1 package and 2 MB on disk. npm audit reported no known vulnerabilities.

How much does node-forge add to a browser bundle?

73.9 KB gzipped (279.9 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does node-forge work with both ESM and CommonJS?

Yes. Both import 'node-forge' and require('node-forge') worked in Node 22 in our run. The package is published as CommonJS.

Does node-forge include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

node-forge or pkijs: which should you use?

pkijs: Choose it for ASN.1, X.509, and CMS APIs built around WebCrypto. Install node-forge for X.509, CSR, ASN.1, or PKCS container work that platform crypto does not cover.

When should you not use node-forge?

You only need common hashes, HMAC, AES, RSA, or Ed25519 on Node; node:crypto uses the platform implementation and adds no package

API stability5/5The forge.pki, forge.asn1, forge.cipher, forge.md, and forge.util namespaces have kept their procedural shape across the 1.x line. Version 1.4.0 changed validation behavior to close security holes but did not redesign the calling API. CommonJS require and ESM interop both worked in our check, though the lack of an exports map keeps packaging old-fashioned.
Docs3/5The README contains working examples for ciphers, RSA, Ed25519, certificates, CSRs, PKCS formats, ASN.1, hashes, and utility buffers. Finding one operation means searching a very long page, and there is no separate typed API reference. Security-sensitive defaults, binary-string conversion, and guidance on choosing platform crypto are easy to miss among legacy transport examples.
Maintenance2/5Version 1.4.0 shipped on 2026-03-24 and the repository was pushed the following day. That release fixed four high-severity security problems after earlier 2025 ASN.1 fixes. GitHub's open counter combines 461 issues and pull requests. The package is not abandoned, but security corrections arriving in clusters and a large backlog argue for conservative, pinned use.
Ecosystem4/5The npm endpoint recorded 37,822,514 downloads for the week ending 2026-08-22, and GitHub reports 5,329 stars. It has broad format coverage and appears transitively in many toolchains, while DefinitelyTyped supplies declarations. New projects have narrower choices for JOSE or X.509, and built-in crypto covers most primitive operations without this dependency.

Use it if

  • You must parse, create, or inspect X.509 certificates and certificate signing requests in JavaScript
  • Your application reads or writes PKCS#12, PKCS#7, PKCS#8, or raw ASN.1 structures
  • The same certificate-processing code must run in Node and a browser without a native module
  • An existing system already stores byte data in Forge ByteBuffer and binary-string forms
Skip it if

Setup reality

Our clean Node 22 install of 1.4.0 completed in 0.4 seconds. It placed 1 package on disk using 2 MB. node-forge has no direct or peer dependencies and is 1,772 KB unpacked. npm audit reported 0 known vulnerabilities. require() and ESM import both worked. The package is CommonJS without an exports map, and we found no TypeScript declarations.

The first practical snag is byte representation. Many Forge methods consume or return binary strings and forge.util.ByteBuffer, not Node Buffer or Uint8Array. Convert a Buffer with buf.toString('binary') and convert back with Buffer.from(bytes, 'binary'). Omitting that encoding silently changes bytes above ASCII. Text input often needs an explicit utf8 argument too.

Version 1.4.0 is the minimum sensible pin for security-sensitive work. Its changelog fixes CVE-2026-33891, CVE-2026-33894, CVE-2026-33895, and CVE-2026-33896. These cover a modInverse infinite loop, RSA PKCS#1 v1.5 signature forgery, non-canonical Ed25519 signatures, and a basicConstraints chain-verification bypass. npm audit was clean for the version we installed; that result does not make older releases safe.

Our esbuild import measured 279.9 KB minified and 73.9 KB gzipped. Build a narrower entry or choose a format-specific library for browser delivery. Synchronous RSA generation and high-iteration PBKDF2 can block the event loop or UI thread. Prefer callbacks, workers, or platform crypto where possible. The license is BSD-3-Clause OR GPL-2.0, so record the selected option in compliance files.

Patterns

Convert Forge bytes and Node buffers convert-bytes

const forgeBytes = inputBuffer.toString('binary');
const buffer = forge.util.createBuffer(forgeBytes);
const outputBuffer = Buffer.from(buffer.getBytes(), 'binary');

Forge binary strings map one character to one byte. UTF-8 conversion corrupts arbitrary key or ciphertext data.

Hash UTF-8 text with SHA-256 hash-text

const digest = forge.md.sha256.create();
digest.update(message, 'utf8');
console.log(digest.digest().toHex());

Pass utf8 for text containing non-ASCII characters. Raw binary input needs different handling.

Derive a key with PBKDF2 and SHA-256 derive-password-key

const salt = forge.random.getBytesSync(16);
const key = forge.pkcs5.pbkdf2(
  password, salt, 600000, 32, forge.md.sha256.create()
);

The synchronous form blocks for the full iteration count. Use the callback form in request handlers and browser interfaces.

Encrypt authenticated data with AES-GCM encrypt-aes-gcm

const key = forge.random.getBytesSync(32);
const iv = forge.random.getBytesSync(12);
const cipher = forge.cipher.createCipher('AES-GCM', key);
cipher.start({ iv, additionalData: aad, tagLength: 128 });
cipher.update(forge.util.createBuffer(plaintext, 'utf8'));
if (!cipher.finish()) throw new Error('encryption failed');
const ciphertext = cipher.output.getBytes();
const tag = cipher.mode.tag.getBytes();

Store the unique IV and authentication tag with the ciphertext. Never reuse an IV with the same GCM key.

Reject modified AES-GCM ciphertext decrypt-aes-gcm

const decipher = forge.cipher.createDecipher('AES-GCM', key);
decipher.start({ iv, additionalData: aad, tagLength: 128, tag });
decipher.update(forge.util.createBuffer(ciphertext));
if (!decipher.finish()) throw new Error('authentication failed');
const plaintext = decipher.output.toString();

finish() returns false on authentication failure. Reading output without checking it accepts untrusted plaintext.

Generate RSA keys asynchronously generate-rsa-keypair

forge.pki.rsa.generateKeyPair({ bits: 2048, workers: -1 }, (error, keys) => {
  if (error) throw error;
  const privatePem = forge.pki.privateKeyToPem(keys.privateKey);
  const publicPem = forge.pki.publicKeyToPem(keys.publicKey);
});

The synchronous form can pause the event loop. workers: -1 lets Forge estimate worker use where supported.

Use RSA-OAEP with SHA-256 encrypt-rsa-oaep

const options = { md: forge.md.sha256.create(), mgf1: { md: forge.md.sha256.create() } };
const encrypted = publicKey.encrypt(secretBytes, 'RSA-OAEP', options);
const decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP', options);

RSA only handles a short payload. Encrypt a random symmetric key rather than a document or request body.

Read fields from a PEM certificate parse-certificate

const certificate = forge.pki.certificateFromPem(pem);
const commonName = certificate.subject.getField('CN')?.value;
const san = certificate.getExtension('subjectAltName');
const expiresAt = certificate.validity.notAfter;

Do not assume commonName exists. Modern identity checks usually use subject alternative names.

Verify a chain against a CA store verify-certificate-chain

const caStore = forge.pki.createCaStore(caPemList);
const chain = presentedPemList.map(pem => forge.pki.certificateFromPem(pem));
forge.pki.verifyCertificateChain(caStore, chain);

Use version 1.4.0 or newer because earlier chain checks could accept a non-CA intermediate missing required constraints.

Create a CSR with a DNS SAN create-csr

const csr = forge.pki.createCertificationRequest();
csr.publicKey = keys.publicKey;
csr.setSubject([{ name: 'commonName', value: 'api.example.com' }]);
csr.setAttributes([{ name: 'extensionRequest', extensions: [{
  name: 'subjectAltName', altNames: [{ type: 2, value: 'api.example.com' }]
}] }]);
csr.sign(keys.privateKey, forge.md.sha256.create());
const pem = forge.pki.certificationRequestToPem(csr);

Certificate authorities read SANs from extensionRequest. Signing without an explicit digest can select an unsuitable legacy default.

Read a password-protected PKCS#12 file open-pkcs12

const der = pfxBuffer.toString('binary');
const asn1 = forge.asn1.fromDer(der);
const p12 = forge.pkcs12.pkcs12FromAsn1(asn1, password);
const bags = p12.getBags({ bagType: forge.pki.oids.certBag });

An empty password and an omitted password can take different parsing paths. Preserve the exporter's exact password behavior.

Create a short-lived development certificate create-self-signed-cert

const cert = forge.pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = '01';
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date(Date.now() + 86400000);
cert.setSubject([{ name: 'commonName', value: 'localhost' }]);
cert.setIssuer(cert.subject.attributes);
cert.setExtensions([{ name: 'basicConstraints', cA: false }, { name: 'subjectAltName', altNames: [{ type: 2, value: 'localhost' }] }]);
cert.sign(keys.privateKey, forge.md.sha256.create());

Use this for local tooling, not a public trust chain. Browsers still require the issuing root to be trusted.

Alternatives

PackageRegistryPick it when
pkijsnpmChoose it for ASN.1, X.509, and CMS APIs built around WebCrypto.
@peculiar/x509npmChoose it when certificate and CSR work needs a smaller modern WebCrypto-oriented surface.
josenpmChoose it when the real requirement is JWT, JWS, JWE, or JWK rather than PKI containers.
jsrsasignnpmCompare it when a pure-JavaScript PKI toolkit is mandatory and format coverage matters more than API size.

More security guides

cryptography · pyjwt · jose · requests-oauthlib · oauthlib · dompurify · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.