本文へスキップ
ecdsa.com

PKCS#12(.pfx/.p12)と PEM の比較|鍵と証明書のまとめ方

PEM は鍵と証明書を別々のテキストブロックとして保持します。PKCS#12 はそれらをパスワードで保護された 1 つのバイナリファイルにまとめ、Windows、macOS、Java、ブラウザーはそのファイルを直接インポートできます。

PKCS#12 と PEM のどちらを使うかは、鍵と証明書を Unix サーバーと独自のキーストアを持つプラットフォームとの間で移すたびに問題になります。PEM ファイルは人が読める別々のブロックで、OpenSSL ベースのソフトウェアがそのまま読み込みます。PKCS#12 ファイル(.p12 または .pfx)は暗号化された 1 つのコンテナーで、OS や Java が一度の操作でインポートします。鍵と証明書は同じで、違うのは包み方です。そして、インポートが成功するかどうかは、包み方に使われたアルゴリズムで決まります。

比較表

PEMPKCS#12(PFX)
仕様RFC 7468(DER のテキストエンコーディング)RFC 7292(PKCS #12 v1.1)、RFC 9579(PBMAC1)
形式テキスト。1 つ以上の BEGIN … END ブロック「セーフバッグ」を収めた DER のバイナリコンテナー
格納できるものファイルに入れたものすべて:鍵、証明書、CRL秘密鍵、証明書、CRL、シークレット(フレンドリ名と鍵 ID 付き)
保護鍵ごとに任意(暗号化された PKCS#8)バッグのパスワードベース暗号化と、完全性を保護する MAC
OpenSSL 3.6 のデフォルトの保護暗号化した鍵には AES-256-CBC による PBES2PBES2、PBKDF2-HMAC-SHA256、AES-256-CBC、反復 2,048 回、HMAC-SHA256 の MAC
OpenSSL の -legacy該当なし証明書は RC2-40、鍵は 3DES、MAC は HMAC-SHA1
一般的な拡張子.pem、.crt、.key.p12、.pfx
利用側OpenSSL、Web サーバー、プロキシ、Go、Python、Node.jsWindows の証明書ストア、macOS のキーチェーン、Java の KeyStore、ブラウザー、signtool

.pfx の中身

PKCS#12 ファイルは PFX 構造です。中身は複数のコンテンツブロックからなる認証付きセーフで、各ブロックにバッグが入っています。秘密鍵は pkcs8ShroudedKeyBag エントリ(暗号化された PKCS#8)に、証明書は certBag エントリ(こちらも暗号化されたブロックに入っていることが多い)に格納され、バッグには鍵とその証明書を結び付ける friendlyName や localKeyId などの属性が付きます。ファイル全体の完全性は、パスワードから導出した MAC で保護されます。RFC 9579(2024年)は PBMAC1 を追加しました。PBMAC1 は、PKCS#12 の旧来の KDF の代わりに PBKDF2 で MAC 鍵を導出します。

PEM にはコンテナーがありません。サーバーには鍵ファイルと証明書(チェーン)ファイル、または複数のブロックを含む 1 つのファイルを設定します。保護は鍵ファイル自体が持つものがすべてで、サーバーでは通常は保護されていません。人がパスワードを入力しなくても、プロセスが鍵を読み込める必要があるからです。

変換

次のスクリプトは OpenSSL 3.6.5 で実行したもので、使い捨ての鍵と証明書を PKCS#12 ファイルにまとめ、再び取り出します。

OpenSSL 3.6:PEM → PKCS#12 → PEM
#!/bin/sh
dir=$(mktemp -d)
cd "$dir" || exit 1
openssl req -new -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout key.pem \
  -subj "/CN=example.com" -days 30 -out cert.pem 2>/dev/null
openssl pkcs12 -export -inkey key.pem -in cert.pem -name demo -passout pass:demo -out bundle.p12
openssl pkcs12 -in bundle.p12 -passin pass:demo -info -noout 2>&1 | grep -E "MAC:|Shrouded|PKCS7 Encrypted"
openssl pkcs12 -in bundle.p12 -passin pass:demo -nodes -out extracted.pem 2>/dev/null
grep -E "^-----BEGIN" extracted.pem
cd / && rm -rf "$dir"
出力
MAC: sha256, Iteration 2048
PKCS7 Encrypted data: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
Shrouded Keybag: PBES2, PBKDF2, AES-256-CBC, Iteration 2048, PRF hmacWithSHA256
-----BEGIN CERTIFICATE-----
-----BEGIN PRIVATE KEY-----

中間証明書を含めるには -certfile chain.pem で渡します。証明書だけを取り出すには -nokeys、鍵だけなら -nocerts を使います。-nodes フラグ(新しいリリースでは -noenc という名前)は鍵を暗号化せずに書き出すため、出力の扱いには注意してください。

互換性:最新のアルゴリズムと旧来のアルゴリズム

OpenSSL 3.0 では、エクスポート時のデフォルトが、RC2-40 と 3DES に HMAC-SHA1 の MAC という組み合わせから、AES-256 と HMAC-SHA256 の MAC に変わりました。多くの利用側が追随したのはその後です。

  • macOS:Apple の Security フレームワークが最新形式の PKCS#12 ファイルをインポートできるのは macOS 15 からです。macOS 14 以前では、パスワードが正しくても「MAC verification failed during PKCS12 import (wrong password?)」というエラーになります。
  • Java:JDK 9 で PKCS#12 がデフォルトのキーストアタイプになりました。JDK 16(および 2021年に公開された 8 と 11 のアップデート)では、Java 自身のデフォルトが AES-256 による PBES2 と HMAC-SHA256 の MAC に切り替わり、現行のバージョンは OpenSSL 3 のファイルを読み込めます。
  • Windows:現行のリリースは AES-256 の PFX ファイルをインポートできます。非常に古いバージョンの Windows Server では、旧形式が必要になる場合があります。
  • OpenSSL 3 で古いファイルを読む場合:証明書が RC2-40 で暗号化されたファイル(OpenSSL 1.1 のデフォルトで、RC2 が組み込まれているときに -legacy でエクスポートした場合もこれになります)は、レガシープロバイダーを読み込む -legacy を付けないと読み込めません。付けない場合、OpenSSL 3.6.5 は RC2-40-CBC について error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported を出力し、Node.js 26 は Unsupported PKCS12 PFX data でファイルを拒否します。3DES と HMAC-SHA1 の MAC だけを使うファイルは、レガシープロバイダーなしで読み込めます。

古いことが分かっているインポート先で最新形式のファイルが読み込めない場合は、openssl pkcs12 -export -legacy … でエクスポートするか、アルゴリズムを明示的に指定し(-keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -macalg SHA1)、そのファイルは転送中の保護が弱いものとして扱ってください。

ここではエラーメッセージが誤解を招きます。「MAC verification failed」や「wrong password」は、たいていパスワードが間違っていることではなく、読み込む側が MAC や暗号化のアルゴリズムに対応していないことを意味します。パスワードを当て推量で試す前に、openssl pkcs12 -info -noout(古いファイルには -legacy を追加)でファイルを調べ、どのアルゴリズムが使われているかを確認してください。

キーストアとしての PKCS#12

Java は PKCS#12 ファイルをキーストアとして扱います。keytool -list -keystore bundle.p12 -storetype PKCS12 でエントリが表示され、friendlyName がエイリアスになります。キーストアは複数の鍵エントリと信頼済みの証明書を保持できますが、PEM ファイルでは命名規則に頼らない限りこれを表現できません。トラストストアは特殊なケースです。証明書だけを含む PKCS#12 のトラストストアでは、証明書を信頼済みとしてマークするために Java 固有の属性が必要なので、確実に作成するには keytool -importcert を使います。ほかの大半のプラットフォームはこうした Java の属性を無視し、localKeyId を照合して鍵と証明書をインポートします。

どちらを選ぶべきか

  • Linux サーバー、リバースプロキシ、Kubernetes の Ingress での TLS:PEM の鍵とフルチェーンの証明書を使います。
  • Windows の IIS、Azure App Service、Key Vault への証明書のインポート:PKCS#12 を使います。
  • Java と Spring Boot の TLS:PKCS#12 のキーストアを使います(最近の Spring Boot は PEM もそのまま受け付けます)。
  • ブラウザーとモバイルデバイス向けのクライアント証明書:強力なパスワードを付けた PKCS#12 を使い、ファイルとは別の経路でパスワードを届けます。
  • signtool や jarsigner によるコード署名:鍵をファイルで持つのであれば PKCS#12 を使います。ただし、2023年以降、公的に信頼されるコード署名鍵はハードウェアに保管しなければなりません。
  • 調査とデバッグ:PEM を使うか、PFX インスペクターを使います。このツールは鍵を表示することなく、バッグとアルゴリズムをローカルで一覧表示します。

移行時の注意点

  1. チェーン全体を含める:サーバーやクライアント向けに PKCS#12 を作成するときは、リーフ証明書を先頭にした順序でチェーン全体を含めます。中間証明書の欠落は、インポート後に起こる失敗で最も多い原因です。
  2. 鍵と証明書を照合する:まとめる前に確認します。鍵・証明書照合ツールは両者の公開鍵を比較します。
  3. 最も古い利用側に合わせてアルゴリズムを選ぶ:その後で引き上げます。古いプラットフォームがなくなったら、最新のデフォルトでエクスポートし直してください。
  4. 強力なランダムパスワードを使う:受け渡しする PKCS#12 ファイルはオフラインで攻撃される可能性があるためです。OpenSSL のデフォルトである PBKDF2 の反復 2,048 回は、パスワード保存の基準からすると少ない回数です。
  5. 取り出した鍵を放置しない:サーバー用に .pfx を PEM に変換したら、ファイルのパーミッションを制限し、一時的なコピーを削除してください。

参考資料

すべての比較