この記事の要点
• TLS ハンドシェイクは、暗号化通信を始める前に鍵交換と認証を行うプロセス
• TLS 1.2 は 2-RTT(往復 2 回)、TLS 1.3 は 1-RTT(往復 1 回)で高速化
• ECDHE 鍵交換 + 証明書検証 + 共通鍵生成の 3 ステップで安全な通信を確立
TLS ハンドシェイクとは
TLS ハンドシェイクは、クライアントとサーバーが暗号化通信を開始する前に、使用する暗号方式の合意、鍵の交換、サーバーの認証を行うプロセスです。
HTTPS との関係: HTTPS は「HTTP over TLS」です。HTTP 通信の前に、まず TLS ハンドシェイクで暗号化チャネルを確立します。
TLS ハンドシェイクの目的
| 目的 | 内容 |
|---|---|
| 暗号方式の合意 | クライアントとサーバーが対応している暗号スイートの中から使用するものを選択 |
| 鍵の交換 | 公開鍵暗号を使って、安全に共通鍵(セッション鍵)を生成・共有 |
| サーバー認証 | サーバーが本物であることを証明書で検証 |
| (オプション)クライアント認証 | クライアント証明書による相互認証 |
ポイント: TLS ハンドシェイクは暗号化される前の平文通信です。ただし、鍵交換には公開鍵暗号を使うため、第三者が盗聴しても共通鍵を知ることはできません。
TLS 1.2 ハンドシェイクの流れ(2-RTT)
TLS 1.2 では、ハンドシェイクに往復 2 回(2-RTT) かかります。
クライアント サーバー
| |
|-- 1. ClientHello ------------------------>|
| |
|<- 2. ServerHello + Certificate + Done ----|
| |
|-- 3. ClientKeyExchange + Finished ------->|
| |
|<- 4. Finished -----------------------------|
| |
|== 5. 暗号化通信開始 ========================|
ステップ 1: ClientHello
クライアントがサポートする暗号スイートとパラメータをサーバーに送信します。
ClientHello:
- TLS Version: 1.2
- Random: (32 バイトの乱数)
- Cipher Suites:
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- ...
- Extensions:
- server_name: example.com (SNI)
- supported_groups: x25519, secp256r1
- ec_point_formats: uncompressed
| フィールド | 内容 |
|---|---|
| TLS Version | サポートする TLS バージョン(1.2, 1.3 など) |
| Random | 後で共通鍵を生成する際に使う乱数 |
| Cipher Suites | 暗号化・鍵交換・MAC のセット |
| SNI(Server Name Indication) | 複数ドメイン対応のため、どのホスト名にアクセスしているかを通知 |
実践メモ: SNI は平文で送られるため、暗号化前に第三者に見られます。これを隠すための ECH(Encrypted Client Hello)という新技術が開発中です。
ステップ 2: ServerHello + Certificate + ServerKeyExchange + ServerHelloDone
サーバーが暗号スイートを選択し、証明書と鍵交換パラメータを返します。
ServerHello:
- TLS Version: 1.2
- Random: (32 バイトの乱数)
- Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
Certificate:
- Server Certificate Chain(サーバー証明書と中間証明書)
ServerKeyExchange:
- ECDHE Public Key(サーバーの ECDHE 公開鍵)
ServerHelloDone:
- (メッセージの終了を示す)
| メッセージ | 内容 |
|---|---|
| ServerHello | 使用する暗号スイート・TLS バージョンを確定 |
| Certificate | サーバーの SSL/TLS 証明書(公開鍵を含む) |
| ServerKeyExchange | ECDHE など鍵交換のパラメータ |
| ServerHelloDone | サーバー側のメッセージ送信完了 |
ステップ 3: ClientKeyExchange + ChangeCipherSpec + Finished
クライアントが鍵交換パラメータを送り、共通鍵を生成して暗号化を開始します。
ClientKeyExchange:
- ECDHE Public Key(クライアントの ECDHE 公開鍵)
ChangeCipherSpec:
- (以降のメッセージを暗号化することを通知)
Finished:
- (ハンドシェイクメッセージの MAC、暗号化されている)
この時点で、クライアントとサーバーは同じ共通鍵(セッション鍵) を持ちます。
ステップ 4: ChangeCipherSpec + Finished(サーバー)
サーバーも暗号化を開始し、ハンドシェイクが完了します。
ChangeCipherSpec:
- (以降のメッセージを暗号化することを通知)
Finished:
- (ハンドシェイクメッセージの MAC、暗号化されている)
ステップ 5: 暗号化通信開始
以降、HTTP リクエスト/レスポンスは共通鍵で暗号化されます。
[クライアント] --暗号化された HTTP リクエスト--> [サーバー]
[クライアント] <--暗号化された HTTP レスポンス-- [サーバー]
ポイント: TLS 1.2 では、ClientHello → ServerHello で 1 往復、ClientKeyExchange → Finished で 1 往復の計 2-RTTかかります。これが TLS 1.3 で 1-RTT に短縮されました。
TLS 1.3 ハンドシェイクの流れ(1-RTT)
TLS 1.3 では、ハンドシェイクが往復 1 回(1-RTT) に短縮されました。
クライアント サーバー
| |
|-- 1. ClientHello + KeyShare ------------->|
| |
|<- 2. ServerHello + KeyShare + Finished ---|
| |
|-- 3. Finished ---------------------------->|
| |
|== 4. 暗号化通信開始 ========================|
主な改善点
| 項目 | TLS 1.2 | TLS 1.3 | 改善内容 |
|---|---|---|---|
| ハンドシェイク往復回数 | 2-RTT | 1-RTT | 接続が 1 往復分高速化 |
| 鍵交換方式 | RSA, DHE, ECDHE | ECDHE のみ | 前方秘匿性(Forward Secrecy)が必須 |
| 暗号化開始タイミング | Finished 後 | ServerHello 直後 | Certificate など一部が暗号化される |
| 古い暗号の削除 | - | RSA 鍵交換、RC4、3DES、MD5 など削除 | セキュリティ強化 |
ステップ 1: ClientHello + KeyShare
TLS 1.3 では、ClientHello に鍵交換パラメータ(KeyShare)を同時に送信します。
ClientHello:
- TLS Version: 1.3
- Random: (32 バイトの乱数)
- Cipher Suites:
- TLS_AES_128_GCM_SHA256
- TLS_AES_256_GCM_SHA384
- Extensions:
- server_name: example.com (SNI)
- key_share: (ECDHE 公開鍵を含む)
- supported_groups: x25519, secp256r1
ポイント: TLS 1.2 では ClientHello → ServerHello の後に鍵交換していましたが、TLS 1.3 では最初から鍵交換パラメータを送ることで 1 往復削減しています。
ステップ 2: ServerHello + KeyShare + Certificate + Finished
サーバーが鍵交換パラメータと証明書を返し、この時点で暗号化を開始します。
ServerHello:
- TLS Version: 1.3
- Random: (32 バイトの乱数)
- Cipher Suite: TLS_AES_128_GCM_SHA256
- key_share: (ECDHE 公開鍵)
EncryptedExtensions:(ここから暗号化)
- Extensions...
Certificate:
- Server Certificate Chain(暗号化されている)
CertificateVerify:
- 証明書の署名検証(暗号化されている)
Finished:
- ハンドシェイクメッセージの MAC(暗号化されている)
ステップ 3: Finished(クライアント)
クライアントが証明書を検証し、ハンドシェイク完了を通知します。
Finished:
- ハンドシェイクメッセージの MAC(暗号化されている)
ステップ 4: 暗号化通信開始
以降、HTTP リクエスト/レスポンスを交換します。
ポイント: TLS 1.3 ではCertificate が暗号化されて送信されるため、盗聴者にサーバー証明書の内容(ドメイン名など)が見えません。プライバシーが向上しています。
TLS 1.2 vs TLS 1.3 比較表
| 項目 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| ハンドシェイク RTT | 2-RTT | 1-RTT |
| 鍵交換 | RSA, DHE, ECDHE | ECDHE のみ(前方秘匿性必須) |
| 暗号スイート数 | 37 種類 | 5 種類(安全なもののみ) |
| Certificate の暗号化 | ✕(平文) | ◎(暗号化) |
| 0-RTT(再接続) | ✕ | ◎(オプション、リプレイ攻撃注意) |
| 削除された暗号 | - | RSA 鍵交換、RC4, 3DES, MD5, SHA-1 |
| セッション再開 | Session ID / Session Ticket | PSK (Pre-Shared Key) |
注意: TLS 1.3 の 0-RTT(再接続時にハンドシェイクをスキップ)は高速ですが、リプレイ攻撃のリスクがあります。冪等でない操作(POST リクエストなど)では使用を避けてください。
ECDHE 鍵交換の仕組み
ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)は、TLS 1.3 で必須となった鍵交換方式です。
ECDHE の流れ
1. クライアントが秘密鍵 a を生成し、公開鍵 A を計算
A = a × G(G は楕円曲線の基準点)
2. サーバーが秘密鍵 b を生成し、公開鍵 B を計算
B = b × G
3. お互いの公開鍵を交換
クライアント → サーバー: A
サーバー → クライアント: B
4. 共通鍵を計算
クライアント: S = a × B
サーバー: S = b × A
→ どちらも S = a × b × G になり、同じ値になる
| 特徴 | 内容 |
|---|---|
| 前方秘匿性(Forward Secrecy) | セッションごとに鍵を生成。過去の通信は秘密鍵が漏れても解読されない |
| Ephemeral(一時的) | ハンドシェイクごとに新しい鍵ペアを生成 |
| 盗聴耐性 | 公開鍵 A, B を盗聴しても、秘密鍵 a, b がないと共通鍵 S を計算できない |
ポイント: RSA 鍵交換では、サーバーの秘密鍵が漏れると過去の通信も全て解読されます。ECDHE ではセッションごとに鍵が変わるため、過去の通信は安全です。
証明書検証の流れ
TLS ハンドシェイク中に、クライアントはサーバー証明書を検証します。
検証項目
| 検証項目 | 内容 |
|---|---|
| 証明書チェーン | サーバー証明書 → 中間証明書 → ルート CA の信頼の連鎖を確認 |
| 有効期限 | 現在日時が証明書の有効期間内か |
| ドメイン名 | 証明書の CN(Common Name)または SAN(Subject Alternative Name)が接続先ドメインと一致するか |
| 失効確認 | CRL(Certificate Revocation List)または OCSP(Online Certificate Status Protocol)で失効していないか |
| 署名検証 | 上位 CA の公開鍵で証明書の署名を検証 |
証明書チェーンの例
[ルート CA](ブラウザに内蔵)
↓ 署名
[中間 CA]
↓ 署名
[サーバー証明書]
サーバー証明書:
- Subject: CN=example.com
- Issuer: CN=Let's Encrypt Authority X3
- Public Key: (RSA 2048 bit)
- Signature: (中間 CA の秘密鍵で署名)
中間証明書:
- Subject: CN=Let's Encrypt Authority X3
- Issuer: CN=DST Root CA X3
- Signature: (ルート CA の秘密鍵で署名)
クライアントは、ルート CA の公開鍵(ブラウザに内蔵)で中間証明書を検証し、中間証明書の公開鍵でサーバー証明書を検証します。
実践メモ: Chrome DevTools の Security タブで、接続先サイトの証明書チェーンを確認できます。Let's Encrypt などの証明書がどのように検証されているか見てみましょう。
SNI(Server Name Indication)
1 つの IP アドレスで複数のドメインを運用する場合、SNI で接続先のホスト名を通知します。
ClientHello:
- Extensions:
- server_name: blog.example.com
サーバーは SNI を見て、対応する証明書を返します。
| SNI なし | SNI あり |
|---|---|
| 1 つの IP = 1 つの証明書 | 1 つの IP = 複数の証明書(ドメインごと) |
| 古い TLS 実装(TLS 1.0 以前) | TLS 1.1 以降、現代の標準 |
注意: SNI は平文で送信されるため、盗聴者に接続先ドメインがわかります。これを防ぐ ECH(Encrypted Client Hello)が TLS 1.3 の拡張として標準化中です。
ALPN(Application-Layer Protocol Negotiation)
TLS ハンドシェイク中に、どのアプリケーション層プロトコルを使うかを決めます。
ClientHello:
- Extensions:
- alpn: h2, http/1.1
ServerHello:
- Extensions:
- alpn: h2
これにより、HTTP/2(h2)と HTTP/1.1 のどちらを使うかをハンドシェイク時に決定できます。
| 用途 | ALPN の値 |
|---|---|
| HTTP/2 | h2 |
| HTTP/1.1 | http/1.1 |
| HTTP/3(QUIC) | h3 |
ポイント: ALPN がなかった時代は、TLS ハンドシェイク後に NPN(Next Protocol Negotiation)を使っていましたが、ALPN に置き換えられました。
まとめ
TLS ハンドシェイクは、暗号化通信を開始する前の鍵交換・認証・暗号方式の合意を行うプロセスです。
| TLS バージョン | ハンドシェイク RTT | 鍵交換 | セキュリティ |
|---|---|---|---|
| TLS 1.2 | 2-RTT | RSA / ECDHE | 古い暗号も残る |
| TLS 1.3 | 1-RTT | ECDHE のみ | 安全な暗号のみ、Certificate 暗号化 |
TLS 1.3 では、1-RTT ハンドシェイク と前方秘匿性必須により、速度とセキュリティの両方が向上しています。現代の Web インフラでは TLS 1.3 の導入が推奨されます。
参考リソース
- RFC 8446 - The Transport Layer Security (TLS) Protocol Version 1.3
- RFC 5246 - The Transport Layer Security (TLS) Protocol Version 1.2
- Cloudflare - What happens in a TLS handshake?
- Mozilla - Server Side TLS
- RFC 6066 - TLS Extensions: SNI