asn1、asn1.js、jsrsasign、node-forge、pem は、Node.js 環境で暗号鍵、証明書、CSR(証明書署名依頼)などの PKI(公開鍵基盤)関連データを扱うためのライブラリ群です。これらは ASN.1(Abstract Syntax Notation One)というデータ構造の規格を解析・生成する機能や、PEM 形式のテキスト変換、RSA/ECDSA などの暗号操作を提供します。node-forge はブラウザと Node.js の両方で動作する包括的な暗号スイートであり、jsrsasign は JWT や X.509 証明書の操作に特化した高機能ライブラリです。一方、asn1 や asn1.js は ASN.1 のデコード・エンコードに焦点を当てた低レベルなツールであり、pem は OpenSSL コマンドをラップして証明書の生成や解析を簡易化するユーティリティです。
現代の Web 開発において、認証(JWT)、通信の暗号化(TLS)、またはデジタル署名の検証は避けて通れない課題です。これらを実現する背後には、ASN.1 というデータ構造の規格と、PEM 形式の鍵管理が存在します。asn1、asn1.js、jsrsasign、node-forge、pem は、これらの技術を JavaScript で扱うための主要なライブラリですが、それぞれが解決しようとしている問題と、提供している抽象化のレベルが大きく異なります。
本稿では、実務的な観点からこれら 5 つのパッケージを比較し、どのようなアーキテクチャでどのライブラリを選ぶべきかを解説します。
まず理解すべきは、これらが「どのレイヤー」を扱っているかです。
asn1 と asn1.js は、ASN.1 という「データの入れ物」の構造そのものを扱う低レベルなライブラリです。これらは暗号アルゴリズム自体の実装ではなく、データをバイナリ(DER)から JavaScript オブジェクトへ変換する役割を担います。
一方、jsrsasign と node-forge は、証明書の生成、署名、検証といった「目的」を達成するための高レベルな API を提供します。
pem は少し異なり、OS にインストールされた OpenSSL コマンドを呼び出すラッパーです。
// asn1.js: ASN.1 スキーマを定義して構造を制御
const asn1 = require('asn1.js');
const MySchema = asn1.define('MySchema', function() {
this.seq().obj(
this.key('version').int(),
this.key('name').utf8()
);
});
const encoded = MySchema.encode({ version: 1, name: 'Alice' }, 'der');
const decoded = MySchema.decode(encoded, 'der');
// jsrsasign: 高レベルな証明書操作
const rs = require('jsrsasign');
const certPem = "-----BEGIN CERTIFICATE-----...";
const certObj = new rs.X509(certPem);
const issuer = certObj.getIssuerString(); // 発行者を簡単に取得
// pem: OpenSSL コマンドのラップ
const pem = require('pem');
pem.createCertificate({ days: 1, selfSigned: true }, function(err, keys) {
console.log(keys.certificate); // OpenSSL が生成した PEM 文字列
});
証明書(X.509)や秘密鍵を扱う際、node-forge と jsrsasign が強力な候補となりますが、そのアプローチは異なります。
node-forge は、TLS ソケットの実装や PKCS#12(.pfx ファイル)の解析など、非常に広範なプロトコルをサポートしています。特に、ブラウザ環境でこれらを実現できる数少ないライブラリです。
jsrsasign は、JWT や JWS、JWE といった Web トークン規格と、X.509 証明書の操作に極めて強い焦点を当てています。ネイティブ依存がないため、AWS Lambda や Cloudflare Workers などのサーバーレス環境でも容易に動作します。
pem は、サーバー上で手軽に証明書を発行したい場合に便利ですが、OpenSSL のインストールが必須であるため、コンテナ環境やフロントエンドでは利用できません。
// node-forge: 完全な証明書オブジェクトの構築と PKCS#12 処理
const forge = require('node-forge');
const pki = forge.pki;
// 証明書の生成(簡略化)
const cert = pki.createCertificate();
cert.publicKey = keys.publicKey;
cert.serialNumber = '01';
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.setSubject([{ name: 'commonName', value: 'example.com' }]);
const pemCert = pki.certificateToPem(cert);
// jsrsasign: JWT の署名と検証
const jwt = require('jsrsasign');
const sHeader = JSON.stringify({ alg: 'RS256', typ: 'JWT' });
const sPayload = JSON.stringify({ sub: '1234', name: 'John' });
const sToken = jwt.jws.JWS.sign('RS256', sHeader, sPayload, privateKey);
const isValid = jwt.jws.JWS.verify(sToken, publicKey);
// pem: CSR(証明書署名依頼)の生成
const pem = require('pem');
pem.createCSR({
country: 'JP',
state: 'Tokyo',
commonName: 'example.com'
}, function(err, result) {
console.log(result.csr); // PEM 形式の CSR
});
フロントエンドアーキテクトとして最も注意すべき点は、実行環境の制約です。
node-forge は「純粋な JavaScript」で書かれているため、ブラウザ、Node.js、Web Workers など、JavaScript が動く場所ならどこでも動作します。これが最大の強みです。
jsrsasign も同様に純粋な JavaScript で実装されており、幅広い環境で動作します。
asn1.js もブラウザ対応しており、暗号通貨ウォレットなどのクライアントサイド処理に適しています。
pem は Node.js の child_process を使用して OS のコマンドを叩くため、ブラウザでは絶対に動作しません。また、サーバー側でも OpenSSL のインストールが必要です。
asn1 は主に Node.js 向けに設計されており、ブラウザでの利用は困難か、追加のポリフィルが必要です。
// node-forge: ブラウザでの SHA-256 ハッシュ計算(例)
// Webpack や Vite でバンドル可能
const md = forge.md.sha256.create();
md.update('foobar');
console.log(md.digest().toHex());
// pem: ブラウザではエラーになる(child_process がないため)
// const pem = require('pem'); // ❌ ブラウザ環境では使用不可
// asn1.js: ブラウザでのバイナリ処理
// バンドルして使用可能
const decoded = MySchema.decode(buffer, 'der');
アーキテクチャ選定において、ライブラリの保守状況は重要です。
asn1 パッケージは、長期間更新が止まっており、実質的にメンテナンスされていない状態です。新しいプロジェクトで ASN.1 処理が必要な場合は、より活発に開発されている asn1.js を選択するべきです。asn1 は既存のレガシーシステムを維持する場合を除き、採用すべきではありません。
pem も、OpenSSL のバージョン差異による挙動の違いや、バイナリ依存の煩わしさから、可能であれば node-forge や jsrsasign での置き換えを検討する価値があります。
// 非推奨: asn1 (メンテナンスが停滞)
// const asn1 = require('asn1');
// 推奨: asn1.js (柔軟なスキーマ定義とアクティブな利用)
const asn1 = require('asn1.js');
// 推奨: node-forge (包括的な機能とブラウザサポート)
const forge = require('node-forge');
技術的な使い勝手の違いとして、データの扱い方があります。
asn1.js は、データ構造を「スキーマ」として定義するアプローチを取ります。これは、複雑なネスト構造を持つプロトコルを扱う際に、コードの可読性と型安全性(概念的に)を高めます。
一方、jsrsasign や node-forge は、特定のタスク(例:「証明書をパースする」「署名する」)に対する関数やメソッドを呼び出すスタイルです。これは日常的なタスクにおいて非常に効率的です。
// asn1.js: スキーマベースのアプローチ
// 構造を事前に定義することで、再帰的なデータもきれいに扱える
const AlgorithmIdentifier = asn1.define('AlgorithmIdentifier', function() {
this.seq().obj(
this.key('algorithm').objid(),
this.key('parameters').optional().any()
);
});
// jsrsasign: 関数呼び出しベースのアプローチ
// 目的が明確で、学習コストが低い
const alg = rs.KEYUTIL.getAlgFromKey(pemKey); // アルゴリズムを取得
const sig = new rs.KJUR.crypto.Signature({ alg: 'SHA256withRSA' });
これらライブラリは異なる目的を持ちますが、最終的には「PEM 形式の文字列」や「DER 形式のバッファ」という共通のフォーマットを介して相互運用可能です。
すべてのライブラリが、入力または出力として PEM 形式(-----BEGIN...)の文字列をサポートしています。
// node-forge で生成した証明書を、jsrsasign で読むことが可能
const forgeCertPem = pki.certificateToPem(cert);
const jsrsaCert = new rs.X509(forgeCertPem);
RSA や EC の秘密鍵は、ライブラリ間で行き来できます。
// node-forge で鍵を生成
const keys = pki.rsa.generateKeyPair(2048);
const pemPrivateKey = pki.privateKeyToPem(keys.privateKey);
// jsrsasign でその鍵を読み込んで署名に使用
const rsaKey = rs.KEYUTIL.getKey(pemPrivateKey);
RSA、ECDSA、SHA-256 などの標準的なアルゴリズムは、主要なライブラリでサポートされています。
// どのライブラリでも SHA-256 ハッシュは計算可能
// node-forge
forge.md.sha256.create().update('data').digest().toHex();
// jsrsasign
rs.crypto.Util.hashString('data', 'sha256');
| 特徴 | asn1 | asn1.js | jsrsasign | node-forge | pem |
|---|---|---|---|---|---|
| 主な用途 | 低レベル ASN.1 解析 | 柔軟な ASN.1 スキーマ定義 | JWT/PKI 高レベル操作 | 包括的暗号・TLS 実装 | OpenSSL ラッパー |
| 抽象度 | 低 | 低〜中 | 高 | 高 | 中(CLI 依存) |
| ブラウザ対応 | ❌ | ✅ | ✅ | ✅ | ❌ |
| 保守状況 | ⚠️ 停滞 | ✅ 良好 | ✅ 良好 | ✅ 良好 | ⚠️ 限定的 |
| 依存関係 | なし | なし | なし | なし | OpenSSL 必須 |
node-forge は、ブラウザとサーバーでコードを共有したい場合や、TLS や PKCS#12 などの複雑なプロトコルを実装する必要がある場合に、最も信頼できる選択肢です。その純粋な JavaScript 実装は、デプロイの headache を大幅に減らしてくれます。
jsrsasign は、JWT の処理や証明書の検証など、特定の PKI タスクをサーバーレス環境や軽量なバックエンドで素早く実装したい場合に最適です。機能の多さと軽さが魅力です。
asn1.js は、独自のバイナリプロトコルを設計したり、暗号通貨関連のデータを扱ったりするなど、ASN.1 構造そのものを制御する必要がある場合にのみ使用すべきです。古い asn1 パッケージは避けてください。
pem は、OpenSSL が確実に存在する管理されたサーバー環境で、スクリプトを素早く書き捨てたい場合を除き、現代のアーキテクチャでは避けるべきです。
最終的には、「ブラウザで動かす必要があるか」「どのレベルの抽象化が必要か」「保守性は十分か」という 3 点を基準に選定することが、堅牢なフロントエンド・バックエンドシステム構築の鍵となります。
LDAP プロトコルや特定のネットワーク通信など、BER 形式の ASN.1 データを低レベルで厳密に制御する必要がある場合に選択します。ただし、メンテナンスが停滞しており、新しいプロジェクトでは asn1.js や他の現代的なライブラリへの移行を検討すべきです。
ASN.1 スキーマを JavaScript オブジェクトとして定義し、双方向(エンコード/デコード)の変換を柔軟に行いたい場合に最適です。暗号通貨やカスタムプロトコルなど、独自のバイナリ構造を扱う必要があるエンジニアリング課題に適しています。
JWT の署名・検証、X.509 証明書の詳細な操作、CSR の生成など、PKI に関連する高レベルな機能を網羅的に必要とする場合に選択します。依存関係なしで動作するため、サーバーレス環境や軽量なバックエンドでの利用に適しています。
ブラウザと Node.js で同一のコードベースを実行させたい場合や、TLS ハンドシェイク、PKCS#12 などの高度な暗号プロトコルを実装する必要がある場合に最適な選択です。純粋な JavaScript で書かれているため、ネイティブモジュールの制約を受けません。
OpenSSL がインストールされたサーバー環境で、簡易的に証明書の生成や CSR の作成を行いたい場合に利用できます。ただし、バイナリ依存がありブラウザでは動作しないため、フロントエンドアーキテクチャでは避けるべきです。
node-asn1 is a library for encoding and decoding ASN.1 datatypes in pure JS. Currently BER encoding is supported; at some point I'll likely have to do DER.
Mostly, if you're actually needing to read and write ASN.1, you probably don't need this readme to explain what and why. If you have no idea what ASN.1 is, see this: ftp://ftp.rsa.com/pub/pkcs/ascii/layman.asc
The source is pretty much self-explanatory, and has read/write methods for the common types out there.
The following reads an ASN.1 sequence with a boolean.
var Ber = require('asn1').Ber;
var reader = new Ber.Reader(Buffer.from([0x30, 0x03, 0x01, 0x01, 0xff]));
reader.readSequence();
console.log('Sequence len: ' + reader.length);
if (reader.peek() === Ber.Boolean)
console.log(reader.readBoolean());
The following generates the same payload as above.
var Ber = require('asn1').Ber;
var writer = new Ber.Writer();
writer.startSequence();
writer.writeBoolean(true);
writer.endSequence();
console.log(writer.buffer);
npm install asn1
MIT.