メインコンテンツまでスキップ

HTTPS終端(SSL/TLS Termination)

所要時間

約40分

学べること

  • HTTPS終端の基本概念と必要性
  • TLSハンドシェイクで何が起きているか
  • なぜ秘密鍵があれば復号できるのか
  • セッション鍵がどう生成されるか
  • 終端位置の選択肢と設計判断

前提知識


1. HTTPS終端とは

1.1 基本概念

**HTTPS終端(SSL/TLS Termination)**とは、暗号化されたHTTPS通信を復号し、平文のHTTPに変換する処理のことです。

┌─────────────────────────────────────────────────────────────┐
│ HTTPS終端の基本イメージ │
├─────────────────────────────────────────────────────────────┤
│ │
│ クライアント │
│ │ │
│ │ HTTPS (暗号化) │
│ ▼ │
│ ┌────────────────┐ │
│ │ 終端ポイント │ ← ここでHTTPSを復号 │
│ │ (LB/Proxy等) │ 秘密鍵を持っている │
│ └────────────────┘ │
│ │ │
│ │ HTTP (平文) │
│ ▼ │
│ ┌────────────────┐ │
│ │ アプリケーション │ │
│ │ サーバー │ │
│ └────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

1.2 なぜ終端が必要なのか

┌─────────────────────────────────────────────────────────────┐
│ HTTPS終端が必要な理由 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 証明書の集中管理 │
│ - 各アプリサーバーに証明書を配布する必要がない │
│ - 証明書更新が1箇所で完結 │
│ - 秘密鍵の管理ポイントを最小化 │
│ │
│ 2. パフォーマンス最適化 │
│ - TLSハンドシェイクの負荷を専用機器で処理 │
│ - アプリサーバーのCPU負荷を軽減 │
│ - SSL/TLSアクセラレーター(専用ハードウェア)の活用 │
│ │
│ 3. L7機能の有効化 │
│ - 暗号化されたままではHTTPヘッダーが読めない │
│ - 復号することでパスベースルーティングが可能 │
│ - WAF(Web Application Firewall)の適用 │
│ │
└─────────────────────────────────────────────────────────────┘

核心的な問い: なぜ「秘密鍵を持っている場所」でしか復号できないのか?

→ これを理解するには、TLSハンドシェイクの仕組みを知る必要があります。


2. TLSハンドシェイクの仕組み

2.1 暗号化の2段階構造

TLS通信は2種類の暗号を組み合わせて使います。

┌─────────────────────────────────────────────────────────────┐
│ TLSで使われる2種類の暗号 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【1. 非対称暗号(公開鍵暗号)】 │
│ │
│ - RSA、ECDH など │
│ - 公開鍵で暗号化 → 秘密鍵でのみ復号可能 │
│ - 処理が重い(CPU負荷大) │
│ - 用途: 鍵交換(セッション鍵を安全に共有) │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【2. 対称暗号(共通鍵暗号)】 │
│ │
│ - AES-GCM、ChaCha20-Poly1305 など │
│ - 同じ鍵で暗号化・復号 │
│ - 処理が軽い(高速) │
│ - 用途: 実際のデータ通信 │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【組み合わせる理由】 │
│ │
│ 非対称暗号: 安全だが遅い → 鍵交換にだけ使う │
│ 対称暗号: 速いが鍵共有が課題 → データ通信に使う │
│ │
│ → 非対称暗号で「対称暗号の鍵」を安全に共有し、 │
│ 以降は対称暗号で高速に通信する │
│ │
└─────────────────────────────────────────────────────────────┘

2.2 TLS 1.2 ハンドシェイク(RSA鍵交換)

まず古典的なRSA鍵交換を理解します。

┌─────────────────────────────────────────────────────────────┐
│ TLS 1.2 ハンドシェイク(RSA鍵交換) │
├─────────────────────────────────────────────────────────────┤
│ │
│ クライアント サーバー │
│ │ │ │
│ │ │ 秘密鍵を保持 │
│ │ │ │
│ Step 1 │ ──── ClientHello ─────────────► │ │
│ │ 対応する暗号スイート一覧 │ │
│ │ クライアントランダム値 │ │
│ │ │ │
│ Step 2 │ ◄──── ServerHello ───────────── │ │
│ │ 選択した暗号スイート │ │
│ │ サーバーランダム値 │ │
│ │ │ │
│ Step 3 │ ◄──── Certificate ──────────── │ │
│ │ サーバー証明書(公開鍵を含む) │ │
│ │ │ │
│ │ 【クライアント側で検証】 │ │
│ │ - 証明書チェーンの検証 │ │
│ │ - 有効期限の確認 │ │
│ │ - ドメイン名の一致確認 │ │
│ │ │ │
│ Step 4 │ ──── ClientKeyExchange ───────► │ │
│ │ Pre-Master Secret │ │
│ │ (サーバーの公開鍵で暗号化) │ │
│ │ │ │
│ │ 【サーバー側で復号】 │
│ │ 秘密鍵でPre-Master Secretを取得 │
│ │ │ │
│ │ 【両側で同じ計算】 │ │
│ │ Master Secret = PRF( │ │
│ │ Pre-Master Secret, │ │
│ │ "master secret", │ │
│ │ ClientRandom + ServerRandom │ │
│ │ ) │ │
│ │ │ │
│ │ Session Key = PRF(Master Secret, ...) │
│ │ │ │
│ Step 5 │ ──── Finished ────────────────► │ │
│ │ ◄──── Finished ─────────────────│ │
│ │ (セッション鍵で暗号化) │ │
│ │ │ │
│ │═══════════════════════════════════│ │
│ │ 暗号化通信開始 │ │
│ │ (セッション鍵で暗号化) │ │
│ │ │ │
└─────────────────────────────────────────────────────────────┘

2.3 なぜ秘密鍵がないと復号できないのか

┌─────────────────────────────────────────────────────────────┐
│ 秘密鍵の役割 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【RSA鍵交換の場合】 │
│ │
│ クライアント: │
│ Pre-Master Secret(ランダムな値)を生成 │
│ ↓ │
│ サーバーの公開鍵で暗号化して送信 │
│ ↓ │
│ ネットワーク上では暗号化されたデータ │
│ ↓ │
│ サーバー: │
│ 秘密鍵で復号してPre-Master Secretを取得 │
│ ↓ │
│ Pre-Master Secret からセッション鍵を導出 │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【秘密鍵がないと何が起きるか】 │
│ │
│ 攻撃者がネットワーク上の通信を傍受しても: │
│ │
│ ✗ 暗号化されたPre-Master Secretは復号できない │
│ → 秘密鍵がないから │
│ │
│ ✗ セッション鍵がわからない │
│ → Pre-Master Secretがわからないから │
│ │
│ ✗ 実際の通信内容が読めない │
│ → セッション鍵がわからないから │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【終端ポイントに秘密鍵が必要な理由】 │
│ │
│ HTTPS終端 = TLSハンドシェイクを処理する │
│ = Pre-Master Secretを復号する │
│ = 秘密鍵が必要 │
│ │
│ だからロードバランサーに証明書(秘密鍵)を置く必要がある │
│ │
└─────────────────────────────────────────────────────────────┘

2.4 TLS 1.3 と ECDHE(現代の方式)

TLS 1.3 では RSA鍵交換は廃止され、**ECDHE(楕円曲線ディフィー・ヘルマン)**が標準になりました。

┌─────────────────────────────────────────────────────────────┐
│ TLS 1.3 ハンドシェイク(ECDHE) │
├─────────────────────────────────────────────────────────────┤
│ │
│ クライアント サーバー │
│ │ │ │
│ Step 1 │ ──── ClientHello ─────────────► │ │
│ │ 対応する暗号スイート │ │
│ │ クライアントの一時公開鍵 │ ← ECDHE用 │
│ │ │ │
│ Step 2 │ ◄──── ServerHello ───────────── │ │
│ │ 選択した暗号スイート │ │
│ │ サーバーの一時公開鍵 │ ← ECDHE用 │
│ │ │ │
│ │ ◄──── Certificate ───────────── │ │
│ │ ◄──── CertificateVerify ─────── │ │
│ │ (秘密鍵で署名して身元証明) │ │
│ │ │ │
│ │ 【両側で同じ計算(Diffie-Hellman)】 │
│ │ │ │
│ │ 共有秘密 = ECDH( │ │
│ │ 自分の一時秘密鍵, │ │
│ │ 相手の一時公開鍵 │ │
│ │ ) │ │
│ │ │ │
│ │ セッション鍵 = HKDF(共有秘密, ...) │ │
│ │ │ │
│ Step 3 │ ──── Finished ────────────────► │ │
│ │ ◄──── Finished ─────────────────│ │
│ │ │ │
│ │═══════════════════════════════════│ │
│ │ 暗号化通信開始 │ │
│ │ │ │
└─────────────────────────────────────────────────────────────┘

【RSAとの違い】

RSA鍵交換:
- 証明書の秘密鍵でPre-Master Secretを復号
- 秘密鍵が漏洩すると過去の通信も復号可能(前方秘匿性なし)

ECDHE:
- 証明書の秘密鍵は署名(身元証明)にのみ使用
- セッション鍵は一時鍵から導出
- 一時鍵は接続ごとに破棄 → 前方秘匿性(Forward Secrecy)
- 秘密鍵が漏洩しても過去の通信は復号不可

2.5 セッション鍵の導出

┌─────────────────────────────────────────────────────────────┐
│ セッション鍵の導出過程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【入力】 │
│ - Pre-Master Secret(RSA)または 共有秘密(ECDHE) │
│ - Client Random(クライアントが生成した乱数) │
│ - Server Random(サーバーが生成した乱数) │
│ │
│ 【導出過程】 │
│ │
│ ┌──────────────────┐ │
│ │ Pre-Master Secret │ │
│ │ または 共有秘密 │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ PRF / HKDF(鍵導出関数) │ │
│ │ + Client Random │ │
│ │ + Server Random │ │
│ └────────┬─────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Master Secret │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ PRF / HKDF(鍵導出関数) │ │
│ └────────┬─────────────────────────────┘ │
│ │ │
│ ├──► Client Write Key(クライアント→サーバー暗号化)│
│ ├──► Server Write Key(サーバー→クライアント暗号化)│
│ ├──► Client Write IV(初期化ベクトル) │
│ └──► Server Write IV(初期化ベクトル) │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【ポイント】 │
│ │
│ - クライアントとサーバーが同じ入力から同じ鍵を導出 │
│ - ネットワーク上で鍵そのものは送信されない │
│ - 送受信で別の鍵を使用(方向ごとに暗号化) │
│ │
└─────────────────────────────────────────────────────────────┘

3. 終端での復号処理

3.1 終端ポイントで何が起きているか

┌─────────────────────────────────────────────────────────────┐
│ 終端ポイントの処理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【受信時(クライアント → LB)】 │
│ │
│ 1. TLSハンドシェイク │
│ - ClientHello を受信 │
│ - 証明書(秘密鍵含む)を使って応答 │
│ - セッション鍵を導出 │
│ │
│ 2. 暗号化データの受信 │
│ - TLSレコードを受信 │
│ - セッション鍵で復号 │
│ - 平文のHTTPリクエストを取得 │
│ │
│ 3. バックエンドへ転送 │
│ - 平文HTTPをアプリサーバーへ転送 │
│ - または別の証明書で再暗号化 │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【送信時(LB → クライアント)】 │
│ │
│ 1. バックエンドから応答受信 │
│ - 平文HTTPレスポンスを受信 │
│ │
│ 2. 暗号化 │
│ - セッション鍵で暗号化 │
│ - TLSレコードとしてクライアントへ送信 │
│ │
└─────────────────────────────────────────────────────────────┘

3.2 TLSレコードの構造

実際に暗号化されるのは「TLSレコード」という単位です。

┌─────────────────────────────────────────────────────────────┐
│ TLSレコードの構造 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【TLSレコード】 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Content Type │ Version │ Length │ Payload │ │
│ │ (1 byte) │ (2 bytes)│(2 bytes)│ (可変長) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Content Type: │
│ 0x14 = ChangeCipherSpec │
│ 0x15 = Alert │
│ 0x16 = Handshake │
│ 0x17 = Application Data ← HTTPデータはここ │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【Application Dataの暗号化(AES-GCM の場合)】 │
│ │
│ 平文: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ HTTP Request/Response │ │
│ │ GET /api/users HTTP/1.1 │ │
│ │ Host: example.com │ │
│ │ ... │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ AES-GCM暗号化 │
│ 暗号文: │
│ ┌────────────┬──────────────────────────┬──────────┐ │
│ │ Nonce │ Ciphertext │ Tag │ │
│ │ (12 bytes)│ (暗号化されたデータ) │ (16 bytes)│ │
│ └────────────┴──────────────────────────┴──────────┘ │
│ │
│ - Nonce: 一意の値(同じ鍵で同じNonceは使わない) │
│ - Tag: 認証タグ(改ざん検知用) │
│ │
└─────────────────────────────────────────────────────────────┘

3.3 復号の具体的な流れ

┌─────────────────────────────────────────────────────────────┐
│ 復号処理の詳細 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. TLSレコードヘッダーを読む │
│ → Content Type = 0x17 (Application Data) │
│ → Length = 1234 bytes │
│ │
│ 2. 暗号文を取得 │
│ → Nonce (12 bytes) │
│ → Ciphertext (1234 - 12 - 16 = 1206 bytes) │
│ → Auth Tag (16 bytes) │
│ │
│ 3. 認証と復号(AES-GCM) │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ AES-GCM-Decrypt( │ │
│ │ key = Server Write Key, │ │
│ │ nonce = Nonce, │ │
│ │ ciphertext = Ciphertext, │ │
│ │ aad = TLSレコードヘッダー, │ │
│ │ tag = Auth Tag │ │
│ │ ) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ → 認証タグが一致しなければ通信拒否(改ざん検知) │
│ → 一致すれば平文を取得 │
│ │
│ 4. 平文HTTPを処理 │
│ → GET /api/users HTTP/1.1 │
│ → Host: example.com │
│ → ... │
│ │
└─────────────────────────────────────────────────────────────┘

4. 終端位置の選択肢

4.1 終端パターンの比較

┌─────────────────────────────────────────────────────────────┐
│ HTTPS終端の3つのパターン │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【パターン1: エッジ終端(Edge Termination)】 │
│ │
│ クライアント ──HTTPS──► LB ──HTTP──► App │
│ ↑ │
│ 秘密鍵はここにある │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【パターン2: 再暗号化(Re-encryption)】 │
│ │
│ クライアント ──HTTPS──► LB ──HTTPS──► App │
│ ↑ ↑ │
│ 外部用秘密鍵 内部用秘密鍵 │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【パターン3: パススルー(Passthrough)】 │
│ │
│ クライアント ──HTTPS──────────────────► App │
│ │ ↑ │
│ LB (L4転送のみ) 秘密鍵はここにある │
│ 秘密鍵なし │
│ │
└─────────────────────────────────────────────────────────────┘

4.2 パターン選択と秘密鍵の配置

┌─────────────────────────────────────────────────────────────┐
│ パターン別の秘密鍵配置 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【エッジ終端】 │
│ │
│ 秘密鍵の場所: ロードバランサー │
│ 管理ポイント: 1箇所 │
│ 内部通信: 平文(VPC内で信頼) │
│ │
│ メリット: │
│ - 証明書管理が容易 │
│ - アプリサーバーにTLS設定不要 │
│ - L7機能(パスルーティング等)が使える │
│ │
│ デメリット: │
│ - 内部通信が平文(内部攻撃に弱い) │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【再暗号化】 │
│ │
│ 秘密鍵の場所: ロードバランサー + 各アプリサーバー │
│ 管理ポイント: 複数 │
│ 内部通信: 暗号化(内部証明書使用) │
│ │
│ メリット: │
│ - End-to-End暗号化 │
│ - コンプライアンス要件を満たしやすい │
│ │
│ デメリット: │
│ - 証明書管理が複雑 │
│ - CPU負荷が増加(2回の暗号化/復号) │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【パススルー】 │
│ │
│ 秘密鍵の場所: 各アプリサーバー │
│ 管理ポイント: アプリサーバーの数だけ │
│ LBの役割: L4転送のみ(暗号化に関与しない) │
│ │
│ メリット: │
│ - LBに秘密鍵を置かなくてよい │
│ - アプリがクライアント証明書を直接検証可能(mTLS) │
│ │
│ デメリット: │
│ - L7機能が使えない │
│ - 証明書の分散管理が必要 │
│ │
└─────────────────────────────────────────────────────────────┘

4.3 パターン選択のガイドライン

┌─────────────────────────────────────────────────────────────┐
│ どのパターンを選ぶか │
├─────────────────────────────────────────────────────────────┤
│ │
│ Q1: L7機能(パスベースルーティング、WAF等)が必要? │
│ │ │
│ ├─ Yes ──► エッジ終端 または 再暗号化 │
│ │ │
│ └─ No ──► Q2へ │
│ │
│ Q2: 内部通信の暗号化が必須?(コンプライアンス要件等) │
│ │ │
│ ├─ Yes ──► 再暗号化 または パススルー │
│ │ │
│ └─ No ──► エッジ終端(最もシンプル) │
│ │
│ Q3: アプリがクライアント証明書を直接検証する必要がある? │
│ │ │
│ ├─ Yes ──► パススルー │
│ │ │
│ └─ No ──► 上記の判断に従う │
│ │
└─────────────────────────────────────────────────────────────┘

5. TLSセッション再利用

5.1 ハンドシェイクのオーバーヘッド

┌─────────────────────────────────────────────────────────────┐
│ TLSハンドシェイクのコスト │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【フルハンドシェイク】 │
│ │
│ - RTT(往復遅延): 1.5〜2 RTT │
│ - CPU: RSA復号 or ECDHE計算 │
│ - 毎回やると遅い │
│ │
│ 例: RTT = 50ms の場合 │
│ TLS 1.2: 2 RTT = 100ms の追加レイテンシ │
│ TLS 1.3: 1 RTT = 50ms の追加レイテンシ │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【セッション再利用で解決】 │
│ │
│ 1. Session ID / Session Ticket │
│ - 以前のセッション情報を再利用 │
│ - ハンドシェイクを短縮 │
│ │
│ 2. TLS 1.3 0-RTT (Early Data) │
│ - 最初のパケットからデータ送信可能 │
│ - ただしリプレイ攻撃のリスクあり │
│ │
└─────────────────────────────────────────────────────────────┘

5.2 終端ポイントでのセッション管理

┌─────────────────────────────────────────────────────────────┐
│ セッション再利用の設定 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【Nginx設定例】 │
│ │
│ ssl_session_cache shared:SSL:10m; # 10MBの共有キャッシュ │
│ ssl_session_timeout 1h; # セッション有効期間 │
│ ssl_session_tickets on; # Session Ticket有効化 │
│ │
│ ────────────────────────────────────────────────────────── │
│ │
│ 【複数LBでのセッション共有問題】 │
│ │
│ LB1 ─┐ │
│ ├─► クライアント │
│ LB2 ─┘ │
│ │
│ 問題: LB1でセッション確立 → LB2に振り分けられると再接続 │
│ │
│ 解決策: │
│ 1. 同一クライアントを同一LBに固定(スティッキーセッション)│
│ 2. セッションチケット鍵をLB間で共有 │
│ 3. 分散セッションストア(Redis等) │
│ │
└─────────────────────────────────────────────────────────────┘

6. 確認コマンド

6.1 TLS接続の確認

# サーバーのTLS設定を確認
openssl s_client -connect example.com:443 -servername example.com

# TLSバージョンを指定して接続
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

# 使用される暗号スイートを確認
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES256-GCM-SHA384'

# セッション再利用のテスト
openssl s_client -connect example.com:443 -sess_out /tmp/session.pem
openssl s_client -connect example.com:443 -sess_in /tmp/session.pem
# "Reused, TLSv1.3" と表示されれば再利用成功

6.2 証明書の確認

# 証明書チェーンの表示
openssl s_client -connect example.com:443 -showcerts

# 証明書の詳細
echo | openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -noout -text

# 証明書の有効期限
echo | openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -noout -dates

# SANs(Subject Alternative Names)の確認
echo | openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -noout -ext subjectAltName

まとめ

学んだこと

  • TLSは非対称暗号(鍵交換)と対称暗号(データ通信)を組み合わせる
  • 秘密鍵はPre-Master Secret(RSA)の復号 or 署名(ECDHE)に使われる
  • セッション鍵は両者が同じ入力から導出する(ネットワーク上で送信しない)
  • 終端ポイントには秘密鍵が必要(だからLBに証明書を置く)
  • 終端パターンは要件に応じて選択する

重要なポイント

1. 暗号化の仕組み
- 非対称暗号: 鍵交換に使用(遅いが安全)
- 対称暗号: データ通信に使用(速い)
- 両方を組み合わせて使う

2. 秘密鍵の役割
- RSA: Pre-Master Secretの復号
- ECDHE: 身元証明の署名(前方秘匿性あり)
- 秘密鍵がある場所でしか終端できない

3. 終端パターンの選択
- エッジ終端: シンプル、L7機能あり
- 再暗号化: End-to-End暗号化
- パススルー: L4転送、mTLS対応

次のステップ

参考リンク