本文へスキップ
ecdsa.com

自己署名証明書と CA 署名証明書の比較|それぞれが適した場面

暗号化の強さはどちらも同じです。違うのは、誰が鍵を保証するのか、クライアントがどのようにしてそれを信頼するようになるのか、そしてローテーションしたときに何が壊れるのかです。

自己署名か CA 署名かは、公開鍵と名前の結び付きを誰が保証するのかという問題です。自己署名証明書はその証明書自身の鍵で署名されているため、証明できるのは保有者がその鍵を持っていることだけです。クライアントがこれを信頼するのは、まさにその証明書を信頼するよう設定されている場合に限られます。CA 署名の証明書は、クライアントがすでにルートを信頼している認証局によって署名されています。インターネット向けのパブリック CA か、自分で運用して配布するプライベート CA のどちらかです。

比較表

自己署名プライベート CAパブリック CA
リーフ証明書に署名するものリーフ証明書自身の鍵自分のルート CA または中間 CAブラウザーと OS のルートストアに含まれる CA
クライアントが信頼するようになる方法各クライアントがこの証明書そのものをピンニングまたはインストールルートを 1 つインストール(MDM、構成管理、コンテナーイメージ)デフォルトで信頼済み
リーフ証明書のローテーションすべてのクライアントの更新が必要クライアントからは透過的クライアントからは透過的
失効なし。すべてのクライアントから削除独自の CRL、OCSP、または短い有効期間CA が公開する CRL
記載できる名前任意ルートの制約の範囲内で任意公開ドメイン名とパブリック IP アドレスのみ
有効期間の上限ブラウザーによる上限はなし。Apple のプラットフォームでは、2019年7月以降に発行された TLS 証明書を 825 日に制限Apple のプラットフォームでは同じく 825 日が上限現在は 200 日、2029年3月からは 47 日
Certificate Transparency記録されない記録されないすべてのホスト名を含めて公開ログに記録される
コストと手間初期コストはなし。ローテーションのたびに負担大CA の運用とその鍵の保護ACME なら無料。DNS または HTTP による検証が必要
代表的なツールopenssl req -x509、ローカル開発には mkcertstep-ca、HashiCorp Vault PKI、クラウドのプライベート CA、cert-managerLet's Encrypt などの ACME 対応 CA

クライアントが証明書を信頼するかどうかを決める仕組み

パス検証(RFC 5280 §6)は、サーバーの証明書からクライアントのルートストアにあるトラストアンカーまでチェーンを構築し、その過程で各署名、有効期間、基本制約、鍵用途、名前制約を確認します。自己署名証明書は発行者とサブジェクトが同じで、自身の鍵で署名されているため、構築できるチェーンはその証明書自体だけです。検証に成功するのは、その証明書自体がクライアントにとってトラストアンカーである場合に限られます。自己署名証明書を「ピンニングする」あるいは「インストールする」とは、まさにこのことを意味します。

CA 署名のリーフ証明書は中間 CAによって署名され、その中間 CA はさらにルート CAへとチェーンでつながります。クライアントはリーフ証明書を事前に知っておく必要がなく、必要なのはルートだけです。この間接性があるからこそ、ローテーション、失効、自動化が可能になります。

次のスクリプトを OpenSSL 3.6.5 で実行すると、自己署名証明書、.internal 配下の名前に制限されたプライベートルート、そのルートで署名された 2 つのリーフ証明書が作成されます。

自己署名証明書、プライベート CA、名前制約
#!/bin/sh
dir=$(mktemp -d)
cd "$dir" || exit 1
P256="-newkey ec -pkeyopt ec_paramgen_curve:P-256 -noenc"

# 1. Self-signed server certificate
openssl req -x509 $P256 -keyout self.key -out self.pem -days 30 \
  -subj "/CN=dev.internal" \
  -addext "subjectAltName=DNS:dev.internal,IP:127.0.0.1" \
  -addext "basicConstraints=critical,CA:FALSE" 2>/dev/null
openssl verify self.pem
openssl verify -CAfile self.pem self.pem

# 2. Private root CA limited to .internal names
openssl req -x509 $P256 -keyout ca.key -out ca.pem -days 3650 \
  -subj "/CN=Example Internal Root" \
  -addext "keyUsage=critical,keyCertSign,cRLSign" \
  -addext "nameConstraints=critical,permitted;DNS:.internal" 2>/dev/null

# 3. Two server certificates signed by that CA
for name in api.internal www.example.com; do
  openssl req -x509 $P256 -CA ca.pem -CAkey ca.key -keyout "$name.key" \
    -out "$name.pem" -days 90 -subj "/CN=$name" \
    -addext "subjectAltName=DNS:$name" \
    -addext "basicConstraints=critical,CA:FALSE" \
    -addext "extendedKeyUsage=serverAuth" 2>/dev/null
done
openssl verify api.internal.pem
openssl verify -CAfile ca.pem api.internal.pem
openssl verify -CAfile ca.pem www.example.com.pem
cd / && rm -rf "$dir"
出力
CN=dev.internal
error 18 at 0 depth lookup: self-signed certificate
error self.pem: verification failed
self.pem: OK
CN=api.internal
error 20 at 0 depth lookup: unable to get local issuer certificate
error api.internal.pem: verification failed
api.internal.pem: OK
CN=www.example.com
error 47 at 0 depth lookup: permitted subtree violation
error www.example.com.pem: verification failed

自己署名証明書の検証に成功するのは、その証明書自体をトラストアンカーとして渡した場合だけです。プライベート CA のリーフ証明書はルートを信頼すれば検証に成功し、ルートの名前制約によって、このルートは www.example.com を保証できません。これにより、CA の鍵が万一悪用されても被害が限定されます。なお、openssl req -x509 は設定ファイルにより、デフォルトで証明書を CA としてマークします。CA:FALSE を明示することで、自己署名のサーバー証明書が発行者として使われるのを防いでいます。

信頼していない証明書に対するクライアントのエラー

トラストストアに何も追加しない状態で、それぞれの証明書を提示するローカルサーバーに 5 つのクライアントから接続しました(2026年10月20日、macOS)。

クライアント自己署名証明書信頼されていないプライベート CA のリーフ証明書
Node.js 26.10DEPTH_ZERO_SELF_SIGNED_CERTUNABLE_TO_VERIFY_LEAF_SIGNATURE
Python 3.14(OpenSSL 3.6.5)certificate verify failed: self-signed certificatecertificate verify failed: unable to get local issuer certificate
Go 1.25(macOS の検証機能)x509: certificate signed by unknown authorityx509: “localhost” certificate is not trusted
curl 8.7.1(LibreSSL)(60) SSL certificate problem: self signed certificate(60) SSL certificate problem: unable to get local issuer certificate
OpenSSL 3.6.5 s_clientVerify return code: 18 (self-signed certificate)Verify return code: 20 (unable to get local issuer certificate)

ブラウザーには NET::ERR_CERT_AUTHORITY_INVALID(Chrome)または MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT(Firefox)が表示されます。検証を無効にすること(NODE_TLS_REJECT_UNAUTHORIZED=0、verify=False、InsecureSkipVerify、curl -k)は決して解決策ではありません。そのプロセスが行うすべての接続から認証が失われてしまいます。代わりに、信頼すべき証明書またはルートをクライアントに渡すか(Node.js では ca:、Python では cafile=、Go ではカスタムの RootCAs プール、curl では --cacert)、ルートを OS の証明書ストアにインストールしてください。Subject Alternative Name 拡張に DNS 名がない証明書は、別の理由で失敗します。Chrome はバージョン 58(2017年)でコモンネームとの照合をやめ、Apple のプラットフォームも iOS 13 と macOS 10.15 以降で照合しなくなりました。

中間の選択肢:プライベート CA

プライベート CA は、自己署名の主な利点(外部の関係者が不要、任意の内部名を使える、公開ログに記録されない)を保ちながら、その主なコスト(ローテーションのたびに信頼を配布し直すこと)をなくします。実践上のルールは次のとおりです。

  • ルートはオフラインで保管し、中間 CA から発行します。中間 CA が危殆化しても、すべてのクライアントに手を加えずに置き換えられます。
  • デモと同様に名前を内部ゾーンに制約し、pathlen と鍵用途も最小限に絞ります。
  • リーフ証明書の発行を自動化する:step-ca、Vault PKI、cert-manager は ACME または独自の API で発行できるため、リーフ証明書の有効期間を数時間〜数日にでき、失効は不要になります。
  • それでもクライアントのルールは守る:Apple のプラットフォームでは、2019年7月1日以降に発行された TLS 証明書に対し、SAN に DNS 名があること、serverAuth の拡張鍵用途、825 日以下の有効期間が求められ、これはプライベートルートにも適用されます。398 日の上限が適用されるのは、公的に信頼されたルートだけです。
  • ルートの配布を計画する:OS のストア、Java のキーストア、コンテナーイメージ、独自のストアを持つブラウザー(Firefox)、独自のバンドルを同梱するソフトウェア(Python の requests が使う certifi パッケージや、--use-system-ca を付けて起動しない限り組み込みの Mozilla バンドルを使う Node.js)のそれぞれにルートが必要です。

パブリック CA では対応できない場合

パブリック CA は、内部名や予約済み IP アドレスを含む証明書を発行することが認められていません(Baseline Requirements §4.2.2)。そのため、printer.local、シングルラベルのホスト名、10.0.0.5、あるいは ICANN が 2024年7月29日にプライベート用途として恒久的に予約した .internal 配下の名前は、プライベート CA か自己署名証明書でしか扱えません。公開の名前を持つ内部ホスト(たとえば db.corp.example.com)は、インターネットから到達できなくても DNS-01 チャレンジで公開証明書を取得できます。ただしその場合、すべての名前が公開の Certificate Transparency ログに記録され、ログを検索する誰にでも内部のトポロジーが明らかになります。

それぞれの用途

  • 公開 Web サイト、API、メールサーバー:ACME でパブリック CA を使います。
  • 内部サービス、サービスメッシュ、ワークロード間の mTLS:短期のリーフ証明書を発行するプライベート CA を使います。
  • ローカル開発:ツールごとに異なる形で拒否される自己署名証明書ではなく、自分のトラストストアにルートをインストールする mkcert のようなローカル開発用 CA を使います。
  • 初回起動時のデバイスとアプライアンス:ユーザーまたはプロビジョニングシステムがフィンガープリントを一度だけ検証する自己署名証明書を使い、その後プライベート CA に登録します。
  • テストフィクスチャ:テスト自体が生成した自己署名証明書を使い、テスト対象のクライアントに明示的に渡します。
  • mTLS によるパートナー連携や B2B 連携:その関係専用のプライベート CA を使います。汎用の内部ルートは決して使いません。

移行時の注意点

  1. 使用中の自己署名証明書を棚卸しする:内部のエンドポイントをスキャンし、クライアントがピンニングしている証明書を集めます。証明書デコーダーで、発行者、SAN、有効期限を確認できます。
  2. プライベート CA を立ち上げる:オフラインのルートと発行用の中間 CA で構成し、内部ドメインに制約します。証明書署名要求は CSR 生成ツールで生成できます。
  3. ルートを先に配布する:サーバーの切り替えはルートの配布が済んでから始め、サービスごとにリーフ証明書を置き換えます。証明書チェーンビルダーで、各サーバーが中間証明書を送信していることを確認できます。
  4. 安全でない回避策を取り除く:クライアントがルートを信頼するようになったら回避策(-k、verify=False、InsecureSkipVerify)を削除し、再び持ち込まれないようにコードベースを検索します。
  5. 公開向けの名前をパブリック CA に移す:ACME を使います。ピンニングを続ける場合は、証明書ではなく鍵を対象にします。SPKI ピン生成ツールは、同じ鍵で更新しても有効なままのピンを計算します。

参考資料

すべての比較