TLS とセキュリティ 基礎
curl https://api.example.com/user/42 の TCP ハンドシェイクの直後、 クライアントとサーバは 2 つ目のハンドシェイク —— TLS 1.3 ハンドシェイク —— を実行し、 暗号スイートに合意し、鍵を交換し、サーバの身元を証明し、接続を暗号化レコードに切り替える。 その後で初めて実際の HTTP リクエストがワイヤに乗る。 4 セクションで全体像を組み立てる:なぜ TLS が存在するか(機密性、完全性、真正性);暗号の構成要素(対称、非対称、AEAD、ECDHE、HKDF);対話的な TLS 1.3 ハンドシェイクウォークスルー;そして PKI —— 証明書、チェーン、CA、mTLS。 最後にクイックリファレンス。
なぜ TLS か —— 機密性、完全性、真正性
TLS なしには、curl とサーバ間のパス上の誰でも —— カフェの AP、 ビルのルータ、すべての ISP ホップ、データセンタスイッチ —— あらゆるバイトを読み、変更できる。 TLS は 3 つの問題を一度に解決する、そしてそれらを混同するのが最も一般的なセキュリティミス。
2026 年に curl https://api.example.com/user/42 を実行すると、 ノート PC を離れるバイトはワイヤを盗聴する者にとってノイズに見える。HTTPS を外す —— 平文 http:// にする —— と同じリクエストは平文のパケットキャプチャになる: URL、ヘッダ、Cookie、JSON ボディ、レスポンス。TLS はそれを防ぐために存在するが、 「暗号化」というフレーミングはそれが何をするかを過小評価する。
3 つの保証
- 機密性 —— 暗号化がパス上のあらゆる受動的観察者からペイロードを隠す。 接続ごとに 1 つの鍵を持つ AES-128-GCM は、ワイヤからバイトを読む攻撃者が受け取る暗号文が 計算的にランダムと区別できないことを意味する。
- 完全性 —— アクティブな攻撃者は転送中のビットを検出されずに反転できない。 AEAD(Authenticated Encryption with Associated Data)が暗号文の隣にレコードごとの MAC を生成する; 1 ビット反転すると復号は全レコードを拒否する。これが敵対的ネットワーク上での 「レスポンスにスクリプトを注入する」攻撃を破るもの。
- 真正性 —— クライアントは実際に
api.example.comと話していることを知る、 それを装う中間者ではなく。これは証明書チェーンの仕事:サーバは、クライアントがすでに信頼するルート CA が (直接または中間経由で)署名し、api.example.comを名前として持つ証明書の秘密鍵を 持っていることを証明する。
どれか 1 つを落とすと、他の意味はほとんどない。完全性なしの機密性は、 攻撃者が暗号化バイトを書き換えて平文を破損させることを許す(パディングオラクル攻撃の基礎)。 機密性 + 完全性で真正性なしは、中間者が自身に完全に暗号化されたチャネルを確立し、 復号、プロキシ、再暗号化することを許す —— 間違ったエンドポイントへの「完璧」な暗号化。
スタック内での TLS の位置
TLS は TCP の上(または QUIC の場合、同等物がトランスポート自体に焼き込まれている)に層化される。 TCP 3 ウェイハンドシェイクの直後、次のパケットが TLS ハンドシェイク、 その後暗号化レコードがアプリケーションプロトコル —— HTTP、IMAP、SMTP、AMQP、MQTT、gRPC —— が送りたい何でも運ぶ。TLS は中身を知らないし気にしない; 単に双方向バイトストリームを暗号化、完全性保護されたレコードに包む。
+-----------------------------+ | HTTP / IMAP / AMQP / gRPC | アプリケーションプロトコル(このレベルで平文) +-----------------------------+ | TLS record layer | 5 バイトヘッダ + AEAD 暗号化ペイロード +-----------------------------+ | TCP | 順序通りのバイトストリーム +-----------------------------+ | IP | ベストエフォートパケット +-----------------------------+
レコードレイヤは TLS の下半分:ハンドシェイクがセッション鍵を確立すると、 アプリが送るすべてのバイトは ≤16 KB のレコードに小さなヘッダ (type + version + length)+ AEAD 暗号化ペイロードでスライスされる。 シーケンス番号は接続ごとに暗黙で nonce にフィードされる、 だから同じレコードを 2 回送ると暗号文が異なり、受信者はリプレイを検出する。
「TLS はどこで終端するか?」
終端 = バイトが復号される場所。典型的なプロダクション構成では、api.example.com はロードバランサ(Cloudflare、AWS ALB、Envoy フリート)に解決され、 証明書を保持し、TLS ハンドシェイクを実行し、リクエストを復号し、 その後実際のアプリケーションサーバへ 3 つのプロトコルのいずれかで話す:
- プライベートネットワーク上の平文 HTTP —— VPC 内で一般的; セキュリティをネットワークレベル分離に依存する。1 つのルーティング誤りパケットや 侵害されたホストで、すべてを失う。
- 緩い証明書での再暗号化 TLS —— 内部 CA、より長い有効期限の証明書。 LB はバックエンドに再暗号化するので、バイトはネットワーク上で決して平文にならない。
- サービス ID を持つ mTLS —— 両側が証明書経由で互いに認証する;Section 04 で扱う。 現代のサービスメッシュのデフォルト。
終端点を知ることは重要、なぜならそれが証明書が設定される場所、TLS バージョンポリシーが 強制される場所、そして大半の TLS 関連の障害が住む場所だから(「LB で証明書が期限切れ」は 繰り返し現れるポストモーテムのテーマ)。
要点。「TLS は 3 つの保証を与える:機密性(AEAD 暗号化がペイロードを隠す)、 完全性(AEAD MAC が転送中の任意の改ざんを検出)、真正性 (証明書チェーンが命名されたホストと話していることを証明、中間者ではない)。 レコードレイヤは任意の TCP バイトストリーム —— HTTP、IMAP、gRPC —— を ≤16 KB の 暗号化レコードに包む。TLS は通常ロードバランサで終端する; バックエンドトラフィックは信頼モデルに応じて平文 HTTP、再暗号化 TLS、または mTLS。」
暗号の構成要素 —— 対称、非対称、AEAD、ECDHE
4 つのアイデア、順に:対称暗号(安い、両側が同じ鍵を必要とする); 非対称(高い、共有秘密が不要);AEAD(暗号化 + 完全性が 1 つのプリミティブ); 鍵交換(盗聴者に見られずに対称鍵をどう得るか)。TLS 1.3 はそれぞれから 1 つ選ぶ。
対称暗号 —— 高速バルク暗号化
対称暗号は暗号化と復号に同じ鍵を使う。AES が主力:128 ビットまたは 256 ビット鍵、 現代のあらゆる CPU でハードウェアアクセラレート(Intel AES-NI、ARM 暗号拡張)、 コアあたり容易に 5+ GB/s のスループット。TLS 1.3 の暗号スイート:
- TLS_AES_128_GCM_SHA256 —— AES-128 GCM モード、HKDF に SHA-256。 暗号拡張付きの x86 / ARM ではほぼどこでもデフォルト。
- TLS_AES_256_GCM_SHA384 —— AES-256、同じ形。 一部のコンプライアンス体制が要求する;AES-128 より ~30% 遅い、実世界のセキュリティ向上ゼロ。
- TLS_CHACHA20_POLY1305_SHA256 —— ChaCha20 ストリーム暗号 + Poly1305 認証器。 AES-NI のない CPU(大半の古い電話、一部の組み込み)では AES より速い。 モバイルクライアントが頻繁に選好する。
本質的に全リスト。TLS 1.2 は ~300 の暗号スイートを持っていた; TLS 1.3 はそれを AEAD + ECDHE + HKDF をすべてピン留めする監査済みの組み合わせのほんの一握りに削減した。
非対称暗号 —— 遅い、公開鍵
非対称スキームは鍵ペアを持つ:誰でも知れる公開鍵、所有者だけが持つ秘密鍵。 一方で行った操作はもう一方で取り消す。署名(秘密で署名、公開で検証)と鍵合意に使われる。 対称より ~1000 倍遅い:
- RSA-2048 —— 証明書署名と古いデプロイで依然一般的。 RSA-1024 は原則的に破られている(2030 年頃)。RSA-4096 は大半でオーバキル。
- ECDSA P-256 —— 楕円曲線署名、256 ビット鍵サイズで ~3000 ビット RSA 相当のセキュリティ。 小さな証明書、高速検証、新しいサーバ証明書の現代デフォルト。
- Ed25519 —— Edwards 曲線署名、さらに高速、パラメータの曖昧さなし、 nonce 再利用の落とし穴なし。SSH ホスト鍵で使われる、一部のルート CA が採用しつつある。
- X25519 —— TLS 1.3 鍵交換に使われる楕円曲線 Diffie-Hellman 変種 (注意:署名は Ed25519、鍵交換は X25519 —— 異なる曲線)。
非対称操作はハンドシェイクでのみ使われる —— 接続あたり単一の RSA 復号または ECDSA 署名。 共有秘密が確立されると、その後のすべてのバイトは対称的。
AEAD —— 暗号化 + 完全性が 1 つのプリミティブ
AEAD 以前の構造は暗号化と完全性を別々に行った:encrypt-then-MAC、MAC-then-encrypt、encrypt-and-MAC。 3 つすべてが不注意に実装されると厄介な失敗モードを持つ:
# AEAD 以前が可能にした脆弱性: BEAST(2011) —— TLS 1.0 CBC モード IV 予測可能性 → クッキー回復 POODLE(2014) —— SSLv3 パディングオラクル;データ露呈へのダウングレード攻撃 LUCKY13(2013) —— MAC-then-encrypt CBC のタイミングサイドチャネル CRIME(2012) —— TLS 圧縮 + 選択平文 → ヘッダ回復
AEAD は暗号化と完全性を 1 つのプリミティブにバンドルする: 単一呼び出しが(鍵、nonce、平文、関連データ)を取り(暗号文、タグ)を生成する。 復号は(鍵、nonce、暗号文、タグ、関連データ)を取り、平文を返すか失敗するかのいずれか。 台無しにする「復号してから MAC をチェック」の順序はない。AES-GCM と ChaCha20-Poly1305 は両方とも AEAD。 TLS 1.3 はあらゆるものに AEAD を義務付ける。
鍵交換 —— 対称鍵はどう生まれるか
両側が同じ対称鍵を必要とし、盗聴者はワイヤ上で見える任意のものからそれを導出できてはならない。 3 つの歴史的アプローチ:
- 静的 RSA 鍵交換(TLS 1.2 以前、1.3 で削除): クライアントがランダムな pre-master secret を選び、証明書からのサーバの長期 RSA 公開鍵で 暗号化して送信する。サーバが RSA 秘密鍵で復号する。前方秘匿性なし: 後でサーバの RSA 秘密鍵を取得した者なら誰でも、その証明書で記録されたあらゆる TLS セッションを 復号できる。政府の敵対者はまさにこの理由で暗号化トラフィックを積極的に蓄積する —— 「今収穫、後で復号」。
- DHE —— Diffie-Hellman Ephemeral:両側が接続ごとにランダム秘密を選び、 公開値を交換し、共有秘密を計算する。サーバの長期鍵はハンドシェイクに署名するが、 鍵交換鍵として決して使われない —— だから後でそれを盗んでも過去のトラフィックを復号しない。 前方秘匿性。数学は大きな素数(2048+ ビット)を使い、これを遅くする。 小さすぎる素数で実装されると Logjam 攻撃に脆弱。
- ECDHE —— 楕円曲線 Diffie-Hellman Ephemeral:同じアイデア、しかし楕円曲線上。 ~256 ビット鍵が遥かに低いコストで 3000 ビット DH と同等の強度を与える。TLS 1.3 は ECDHE を義務付ける; X25519 が普遍的なデフォルト曲線。
前方秘匿性が鍵となる特性:今日暗号文を記録し、後でサーバの証明書秘密鍵を取得する攻撃者は、 エフェメラル鍵がどこにも書き留められなかったため、依然として記録されたセッションを復号できない。 前方秘匿性なしには、暗号化トラフィックの耐久性は長期証明書鍵の秘匿性と同程度。
HKDF —— 1 つの秘密を多くにする
ECDHE 共有秘密は 32 バイト。TLS 1.3 は多くの鍵を必要とする: ハンドシェイク暗号化鍵(server-write、client-write)、アプリケーショントラフィック鍵 (server-write、client-write)、exporter secret(トークンバインディングなど用)、 resumption secret(次のセッションの PSK 用)。HKDF(HMAC-based Key Derivation Function)が標準プリミティブ:HKDF-Extract(salt, secret) がエントロピを固定サイズの PRK に圧縮し、 その後 HKDF-Expand(PRK, info, len) を異なる info 文字列で何度も呼べ、 任意の長さの独立した鍵を生成する。出力鍵は計算的に独立 —— 1 つを侵害しても他を露呈しない。
# TLS 1.3 が実際に導出するもの(短縮): shared_secret = ECDH(client_x25519, server_x25519) handshake_secret = HKDF-Extract(salt = early_secret, secret = shared_secret) client_hs_traffic = HKDF-Expand-Label(handshake_secret, "c hs traffic", transcript_hash) server_hs_traffic = HKDF-Expand-Label(handshake_secret, "s hs traffic", transcript_hash) # …… master_secret → application keys → resumption_secret へと続く
要点。「対称暗号(AES-128-GCM、ChaCha20-Poly1305)がバルクデータ暗号化を行う —— 高速、両側が同じ鍵を必要とする。非対称(ECDSA、Ed25519、X25519)は遅く、署名と鍵交換のために ハンドシェイクでのみ使われる。AEAD は暗号化 + 完全性を 1 つのプリミティブに詰める; TLS 1.3 はそれを義務付ける、なぜなら AEAD 以前の構造はパディングオラクル攻撃を生み続けたから。 ECDHE は前方秘匿性を与える:暗号文を記録 + 後で証明書鍵を盗むことが依然として復号しない。 HKDF は 1 つの ECDH 秘密をプロトコルが必要とするすべての異なる鍵に拡張する。」
TLS 1.3 ハンドシェイク —— 暗号化レコードへの 1 ラウンドトリップ
TLS 1.2 は HTTP の最初のバイトが流れる前に 2 ラウンドトリップを必要とした。 TLS 1.3 は 1 つで済む。クライアントが ClientHello で暗号と key share を投機する; サーバが確認し、即座に残りを暗号化する。節約 —— 接続あたり 1 RTT —— が書き直し全体の動機だった。
api.example.com 接続の TCP ハンドシェイクが終わった直後、 両側は順序通りのバイトストリームを持つが暗号化はない。TLS ハンドシェイクはワイヤ上の次のもので、 TLS 1.3 ではきっかり 1 ラウンドトリップに収まる —— 以下のステップがそれをメッセージごとに歩く。
ClientHello で 投機的に key share を送るから。サーバが暗号スイートを選び、自分の share を送り、 ECDH 経由で共有秘密を導出し、ハンドシェイクの残りをハンドシェイクトラフィック鍵で 即座に暗号化する —— 証明書、CertificateVerify(全トランスクリプトに署名するので ダウングレード攻撃が隠せない)、Finished。アプリケーションレコードは HKDF 経由で 導出された別のアプリケーショントラフィック鍵で流れる。なぜ TLS 1.3 は 1 RTT で 1.2 は 2 RTT だったか
TLS 1.2 では、クライアントが ClientHello(暗号を提案)を送り、ServerHello + Certificate + KeyExchange + ServerHelloDone を待ち、 その後自分の ClientKeyExchange + Finished を送り、 サーバの Finished を待つ —— あらゆるアプリケーションデータの前に 2 ラウンドトリップ。 TLS 1.3 は 1 つの重要な変更を行った:クライアントは最初のメッセージで key share を送る、 サーバもサポートすると期待する暗号を投機的に選ぶ。サーバが同意すれば、 即座に共有秘密を導出し、ServerHello 応答はすでに暗号化された他のすべてを含めることができる。 合計ハンドシェイク:1 RTT。
TLS 1.2(2 RTT): TLS 1.3(1 RTT):
client -> ClientHello client -> ClientHello + key_share
server -> ServerHello server -> ServerHello + key_share +
-> Certificate {EE, Cert, CertVerify, Finished}
-> ServerKeyExchange client -> {Finished}
-> ServerHelloDone -> [Application Data]
client -> ClientKeyExchange
-> ChangeCipherSpec
-> Finished
server -> ChangeCipherSpec
-> Finished
client -> [Application Data]0-RTT —— リクエストが最初のパケットに乗る
クライアントがこのサーバに以前接続したことがあり resumption secret を保持していれば、 1 RTT より良くできる:ClientHello と同じパケットで、 クライアントは PSK 派生鍵の下で暗号化された「early data」を含めることができる。 合計:0 RTT。HTTP リクエストは文字通り接続の最初の IP パケットに乗る。
落とし穴:0-RTT はリプレイ脆弱。early-data パケットを捕捉する攻撃者は後でリプレイできる —— TLS 1.3 は 0-RTT に組み込みのアンチリプレイを持たない(CDN 全体で悪くスケールするサーバ側状態を必要とする)。 だから 0-RTT は冪等リクエストにのみ安全。CDN は典型的に GET には 0-RTT を許可するが、 POST/PUT/DELETE には 1-RTT ハンドシェイクを強制する。
TLS 1.3 が削除したもの(とその理由)
最大の設計選択は否定的だった —— 何を削除するか。TLS 1.3 は CVE を生成したことのある すべてのアルゴリズムを削除した:
- 静的 RSA 鍵交換 —— 前方秘匿性なし(Section 02 参照)。 すべての鍵交換は (EC)DHE でなければならない。
- MD5 と SHA-1 をあらゆるハンドシェイクロールで —— 両方とも実用的な衝突攻撃を持つ。
- RC4 —— バイアスのあるキーストリーム、十分なサンプルの後に平文を回復できる。
- CBC モードブロック暗号 —— BEAST、POODLE、LUCKY13 の源。今やすべて AEAD。
- TLS レイヤでの圧縮 —— CRIME を可能にした。アプリケーションレイヤ圧縮(gzip)は 依然として問題ない、なぜなら攻撃者は TLS レコードにできたように HTTP ボディに選択平文を注入できないから。
- 再ネゴシエーション —— 別個の KeyUpdate に置き換えられた。再ネゴシエーションは 2009 年の脆弱性の源だった、そこでサーバが 2 つのクライアントのデータを混同した。
正味の効果:TLS 1.2 の ~300 暗号スイートが TLS 1.3 では ~5 つになった —— 構成上すべて安全。 もう「設定で悪いものを無効化」はない —— 悪いものは単に存在しない。
トランスクリプトの完全性がダウングレード攻撃を破る
CertificateVerify メッセージはサーバの長期秘密鍵での署名で、 これまでのすべてのハンドシェイクメッセージの SHA-256 ハッシュに対するもの —— 「トランスクリプトハッシュ」。 攻撃者が ClientHello を改ざんしてより弱い暗号を強制した場合、サーバが署名するトランスクリプトは クライアントが計算したものと異なり、署名は検証されない。これがダウングレード攻撃の全ファミリ (Logjam、FREAK)に対する防御。
知る価値もある:Finished メッセージはハンドシェイク鍵を使ったトランスクリプト上の MAC、 だから両側が同じトランスクリプトハッシュを計算したことを相互確認する。 転送中の任意の改ざんは MAC を壊し、任意のアプリケーションデータが送られる前に接続は中止される。
openssl で見られるもの
$ openssl s_client -connect api.example.com:443 -tls1_3 -msg CONNECTED(00000005) >>> TLS 1.3, Handshake [length 00ff], ClientHello <<< TLS 1.2, Handshake [length 007a], ServerHello <<< TLS 1.3, Handshake [length 0024], EncryptedExtensions <<< TLS 1.3, Handshake [length 092e], Certificate <<< TLS 1.3, Handshake [length 0114], CertificateVerify <<< TLS 1.3, Handshake [length 0034], Finished >>> TLS 1.3, Handshake [length 0034], Finished --- Server certificate subject=CN = api.example.com issuer=C = US, O = Let's Encrypt, CN = R3 Cipher : TLS_AES_128_GCM_SHA256 Protocol : TLSv1.3 Verification: OK
要点。「TLS 1.3 は 1 RTT、なぜならクライアントが ClientHello で投機的に key share を送り; サーバが暗号を選び、共有秘密を導出し、証明書 + CertificateVerify + Finished をすべて暗号化された ServerHello 応答で送るから。クライアントが Finished を送る;HTTP は次のパケットに乗れる。 0-RTT は PSK を使ってハンドシェイクをスキップするがリプレイ脆弱 —— 冪等リクエストのみ。 CertificateVerify が全トランスクリプトに署名するのでダウングレード攻撃(Logjam、FREAK)は隠せない。」
PKI —— 証明書、チェーン、CA、mTLS
Section 02 の暗号がセキュアなトンネルを確立する;PKI はトンネルのもう一端が実際に 要求したホストかどうかをクライアントに伝えるもの。建造物全体 —— X.509 証明書、 信頼ルート、ACME、失効 —— がその 1 つの質問に正しく答えるために存在する。
X.509 —— 証明書フォーマット
証明書はバイナリ構造(DER エンコードされた ASN.1、通常 PEM アーマー Base64 で表示)で、 公開鍵を 1 つ以上の名前にバインドし、発行者によって署名される。 TLS クライアントが実際に見るフィールド:
- Subject —— レガシーな「所有者」名;
Common Name(CN)フィールドが かつてホスト名を保持した。現代のクライアント(Chrome 2017 以降)は CN を完全に無視する。 - Subject Alternative Name (SAN) —— ホスト名のための現代のフィールド。 単一の証明書が多くの SAN(
api.example.com、www.example.com、*.example.comワイルドカード)を列挙できる。クライアントは要求されたホスト名を このリストと照合する。 - 公開鍵 —— サーバの公開鍵(RSA-2048、ECDSA P-256 など)。 サーバは所有権を証明するため一致する秘密鍵でハンドシェイクに署名する。
- 有効期間 —— Not Before / Not After タイムスタンプ。 パブリック CA(Let's Encrypt など)は 90 日証明書を発行;更新は自動化される必要がある。
- Issuer —— チェーンの次の上の証明書を名前で。
- 拡張 —— Key Usage(digitalSignature、keyEncipherment)、 Extended Key Usage(serverAuth、clientAuth)、Basic Constraints(この証明書は CA か?)、 CRL Distribution Points、OCSP responder URL、SCT リスト(Certificate Transparency)。
- 署名 —— 発行者の上の全部の上に、発行者の秘密鍵で計算された署名。
$ openssl x509 -in cert.pem -noout -text | head -25
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 04:c5:ab:...
Signature Algorithm: ecdsa-with-SHA256
Issuer: C = US, O = Let's Encrypt, CN = R3
Validity
Not Before: Mar 1 00:00:00 2026 GMT
Not After : May 30 23:59:59 2026 GMT
Subject: CN = api.example.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
EC Public Key: (256 bit)
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Subject Alternative Name:
DNS:api.example.com, DNS:www.example.comチェーン —— なぜ中間証明書が必要か
典型的なチェーンは 3 つの深さ:
Leaf: CN = api.example.com (R3 が発行、有効 90 日)
|
| R3 の秘密鍵で署名
v
Intermediate: CN = Let's Encrypt R3 (ISRG Root X1 が発行、有効 ~5 年)
|
| ISRG Root X1 の秘密鍵で署名
v
Root: CN = ISRG Root X1 (自己署名、有効 ~30 年)
^
| あらゆるブラウザ / OS 信頼ストアにプリインストールなぜ中間?リスク分離。ルート CA の秘密鍵はその CA にとって世界で最も貴重な鍵 —— オフラインで保管、HSM 内、安全な施設に。中間のみ署名し、決して離れない。 中間の鍵が侵害されると、CA はその中間を失効させ(伝播に労力を要する)、新しいものを発行する —— しかしルートは安全を保つ。ルートの侵害はあらゆるブラウザと OS が新しい信頼ストアを出荷する必要がある。 ルートは 10-20 年に 1 回ローテートされる;中間はより頻繁。
ハンドシェイク時、サーバは leaf 証明書とそれが知っている任意の中間を送る。 クライアントはチェーンを歩き、次の証明書の公開鍵を使って各署名を検証し、 発行者がローカル信頼ストアにある証明書に到達するまで。
ACME と Let's Encrypt
2016 年以前、証明書を取得するということは CA のウェブサイトでフォームに記入し、 年に $100+ を払い、手動でファイルをインストールすることを意味した。ACME(Automated Certificate Management Environment、RFC 8555)が それらすべてを自動化したプロトコル。Let's Encrypt は ACME をスケールで実行する 無料の CA。フロー:
- あなたの
certbot/acme.sh/ Caddy / Traefik が新鮮な鍵ペアを生成する。 - Let's Encrypt に
api.example.comの証明書を発行するよう要求する。 - Let's Encrypt がクライアントにドメインの制御を証明するよう挑む:
http://api.example.com/.well-known/acme-challenge/xyzで特定のファイルを提供する (HTTP-01)、または DNS TXT レコードを設定する(DNS-01)。 - Let's Encrypt が検証のためチャレンジを取得し、その後署名して証明書を返す(90 日有効)。
- あなたのツールが証明書をインストールし、~60 日で更新ジョブをスケジュールする。
業界への影響:HTTPS デプロイメントは 2014 年のページロードの ~30% から 2026 年の ~95% に。 EV(Extended Validation、「緑のバー」)と OV(Organization Validation)証明書は ほぼ死んでいる、なぜなら Chrome が 2019 年に緑のバーを表示するのをやめたから —— ユーザはとにかく DV と EV を確実に区別できなかった、EV は実世界のセキュリティ利点を与えなかった。
失効 —— 証明書を殺す必要があるとき
証明書は 90 日間発行できるが 7 日目に侵害される可能性がある。クライアントはどう知るか? 3 つのメカニズム、すべて不完全:
- CRL(Certificate Revocation List)—— CA が失効されたシリアル番号の署名付きリストを公開する。 オリジナルのアプローチだった;リストが巨大になった(MB スケール)、ダウンロードコストが支配的だった。 ブラウザは大半が確認をやめた。
- OCSP(Online Certificate Status Protocol)—— クライアントが CA の OCSP responder に 「シリアル番号 X はまだ有効か?」のためにリアルタイム HTTP 要求を行う。証明書あたり余分な RTT を追加し、 閲覧履歴を CA にリークし、responder がダウンしているとブラウザは soft-fail(安全でない)または hard-fail(拒否)のいずれかをしなければならない。実際には大半 soft-fail。
- OCSP stapling —— サーバが定期的に CA から新鮮な OCSP 応答を取得し、 TLS ハンドシェイク中に証明書とともに提供する。クライアントは余分な RTT やプライバシリークなしに 新鮮さの保証を得る。現代のデフォルト。
さらに:Certificate Transparency (CT) —— すべての公的に信頼された証明書は 発行前に追記専用 CT ログに記録されなければならない。ブラウザは十分な SCT(Signed Certificate Timestamps) のない証明書を拒否する。これは誤発行を公開する:CA があなたが要求しなかったドメインの証明書を発行した場合、 ログで見て苦情を言える。CT は 2015 年の Symantec が偽の google 証明書を発行したスキャンダルサイクルを終わらせた。
mTLS —— クライアントも証明書を持つとき
標準 TLS では、サーバのみが証明書を提示する。相互 TLS(mTLS)はクライアントも 提示することを要求する —— 両側が互いに認証する。証明書チェーン検証は両方向で実行される。
ユースケース:
- サービスメッシュ内のサービス間 —— Istio、Linkerd、Consul Connect が あらゆる内部 RPC のために mTLS をデフォルトにする。プロキシサイドカーが証明書発行、ローテーション、 検証を処理する;アプリケーションは平文 HTTP または gRPC 呼び出しを見る。
SPIFFE/SPIREが ID モデルを標準化する:あらゆるワークロードがspiffe://prod/payment-serviceのような SPIFFE ID を取得し、その証明書に埋め込まれる。 - 高セキュリティエンドポイントの API 認証 —— 銀行、政府、B2B 統合。 クライアントの証明書はあらゆる API key よりもそれを強く識別する(API key のように ログファイルを介して流出させることができない)。
- VPN 置換(ゼロトラストネットワーク) —— Cloudflare Access、Tailscale、Google BeyondCorp。 各デバイスが証明書を取得する;アクセスはネットワーク場所ではなく証明書検証によってゲートされる。
証明書ピンニング —— PKI さえ十分でないとき
高価値モバイルアプリ(銀行、メッセージング)では、標準信頼ストアが広すぎる —— 数百の CA、いずれもあなたのドメインに不正な証明書を発行する可能性がある (CT はこれを公開するが防止しない)。ピンニングはアプリに期待される証明書 (またはその公開鍵 SPKI ハッシュ)をハードコードする;ハンドシェイク中に提示された証明書が 一致しない場合、信頼ストアが何を言っても接続は拒否される。
ブラウザは HPKP(HTTP Public Key Pinning)でこれを試したが、 2018 年に非推奨化された —— ピンされた鍵を失うことで自身のサイトをレンガにするのが簡単すぎた。 モバイルピンニングは依然として一般的、なぜならアプリベンダーが更新チャネルを制御し、 新しいピンを出荷できるから。注意:ローテーション漏れがユーザを締め出し、緊急アプリ更新が必要。
要点。「証明書は公開鍵をホスト名に(SAN フィールドで)バインドし、 中間 CA が署名し、クライアントの信頼ストアにあるルート CA が署名する。 leaf は ACME / Let's Encrypt 経由で 90 日ごとにローテートする; ルートは 10 年ごとにローテートする。実際の失効は OCSP stapling + Certificate Transparency。 mTLS はクライアント側証明書認証を追加する —— 内部 RPC のサービスメッシュデフォルト、 VPN のためのゼロトラスト置換。ピンニングは高価値モバイルアプリのために期待される証明書をハードコードする。」
クイックリファレンス
冷たく説明できる価値のある 6 つの核心問題と、コードレビューで一目で見抜くべき 5 つの赤旗。
TLS が提供する 3 つの保証は何で、それぞれをどう提供するか?
機密性 —— AEAD 暗号化(AES-128-GCM または ChaCha20-Poly1305)があらゆる受動的観察者から ペイロードを隠す。完全性 —— 同じ AEAD プリミティブがレコードごとの MAC を生成する; 転送中の任意の改ざんが復号に失敗する。真正性 —— 証明書チェーンがサーバが要求されたホストを 命名する証明書の秘密鍵を保持し、その証明書が(推移的に)クライアントの信頼ストアにあるルートで 署名されていることを証明する。いずれか 1 つを落とすと他はほとんど無用: 真正性がなければ MITM が自身への完璧な暗号化チャネルを実行できる。
なぜ RSA 鍵交換(vs ECDHE)はレガシーと見なされるか?
前方秘匿性がない。静的 RSA 鍵交換では、クライアントがサーバの長期 RSA 公開鍵で pre-master secret を暗号化する。後で RSA 秘密鍵を取得した者なら誰でも、 その証明書で記録されたあらゆる TLS セッションを復号できる —— これが国家アクターのトラフィックキャプチャを動機付ける「今収穫、後で復号」の脅威モデル。 ECDHE は接続ごとに使い捨てのエフェメラル鍵を生成する; 後で長期証明書鍵を盗んでもそれを明らかにしない。TLS 1.3 は静的 RSA を完全に削除した。
TLS 1.3 ハンドシェイクを歩いてください —— 各メッセージに何があるか?
ClientHello:サポートする暗号、X25519 公開鍵、SNI = ホスト名。ServerHello:選択された暗号、 サーバ X25519 公開鍵(これで両側が共有 ECDH 秘密を計算し、HKDF 経由でハンドシェイク鍵を導出できる)。 その後、ハンドシェイク鍵の下で暗号化:EncryptedExtensions、Certificate(leaf + 中間)、 CertificateVerify(ハンドシェイク全トランスクリプト上の署名 —— ダウングレード攻撃を破る)、 Finished(トランスクリプトの MAC)。クライアントがチェーンを検証し、自身の Finished を暗号化して送る。 両側がアプリケーショントラフィック鍵に切り替える。HTTP は次のパケットに乗る。 合計:アプリデータの前に 1 RTT。
「証明書チェーンを検証する」とは何を意味するか —— ブラウザは実際に何をチェックしているか?
順番に 5 つ:(1)Subject Alternative Name リストが接続中のホスト名を含む。 (2)現在時刻が Not Before / Not After 内。(3)Extended Key Usage に serverAuth を含む。 (4)チェーンの各証明書について、次の上の証明書の公開鍵がその署名を正常に検証する; 最上位証明書の発行者がローカル信頼ストアの証明書と一致する。 (5)OCSP staple(または soft-fail OCSP)経由の失効チェック、CT ログからの十分な SCT が存在。 いずれかのステップの失敗はハードエラーでハンドシェイクを中止する —— 「無視して続行」はない。
0-RTT とは何で、なぜ危険か?
0-RTT(early data)は前のセッションの PSK を再利用するクライアントが ClientHello と一緒に 最初のパケットに暗号化されたアプリケーションデータを送ることを許す —— ハンドシェイクを完全にスキップする。 危険:データはリプレイ脆弱。0-RTT パケットを捕捉する攻撃者は後でリプレイできる; TLS 1.3 は early data に組み込みのアンチリプレイを持たない(CDN 全体で悪くスケールする サーバ側状態を必要とする)。冪等リクエストにのみ安全;CDN は典型的に GET には 0-RTT を許可し、 POST / PUT / DELETE には 1-RTT を強制する。
mTLS とは何で、どこで使うか?
相互 TLS —— クライアントも証明書を提示し、サーバはクライアントがサーバを検証するのと同じように クライアントのチェーンを検証する。主な用途:サービスメッシュ内のサービス間認証 (Istio / Linkerd / Consul Connect、しばしば SPIFFE ID 経由)、 API key が弱すぎる高セキュリティ API 認証(銀行、B2B 統合)、 そして VPN を置き換えるゼロトラストネットワークアクセス(Cloudflare Access、Tailscale)。 メッシュケースが最も一般的 —— mTLS は任意の現代の Kubernetes デプロイメントの東西トラフィックのデフォルト。
コードレビューの赤旗
- 本番 HTTP クライアントで証明書検証を無効化。
curl -k、requests.get(..., verify=False)、InsecureSkipVerify: true。これは TLS を「mTLS なしの mTLS」にする:何らかのサーバへの暗号化、MITM を検出する方法なし。 ほぼ常に誰かが証明書エラーに当たって修正する代わりに迂回した兆候。 - TLS の代わりに HTTP + アプリレベル AES「暗号化」。自分で暗号を巻くということは自分でパディングオラクル / リプレイ / nonce 再利用バグを巻くことを意味する。 TLS は 10 年の専門暗号学者の出力;それを使い、アプリケーションロジックをその横ではなく上に置く。
- 公共サービスで古い TLS バージョン(1.0 / 1.1)が有効。BEAST、POODLE、LUCKY13 はそこに住む。現代のブラウザはデフォルトで 1.0 / 1.1 を拒否するが、 サーバ側互換フラグはしばしばまだそれらを許可する。明示的に無効化:nginx の
ssl_protocols TLSv1.2 TLSv1.3。 - 内部 PKI / 信頼配布のない自己署名証明書。あらゆるクライアントが検証無効で動く(赤旗 1 参照)か、コードにピンされた証明書のコピーを持つ (ローテーション悪夢)かのいずれかを意味する。代わりに小さな内部 CA(smallstep、Vault PKI)を立て、 設定管理経由でルート証明書を配布する。
- 非冪等エンドポイントで 0-RTT が有効。0-RTT POST を許可すると、攻撃者が以前にキャプチャされた購入、転送、または DB 変更リクエストを リプレイできる。大半の CDN ではルートごとに 0-RTT を許可リスト化できる —— GET と HEAD にとどめ、変更動詞には決して使わない。