ClientHello と ServerHello で方式と鍵交換材料を決め、サーバー証明書と CertificateVerify で相手を確認し、最後に Finished でここまでのやり取りが改ざんされていないことを検証します。TLSハンドシェイクは何をしているのか
TLSの目的は、盗聴、改ざん、なりすましを防ぎながらアプリケーションデータを送ることです。そのために、最初のハンドシェイクで「どの暗号方式を使うか」「相手は本物か」「この接続だけの暗号鍵は何か」を決めます。
Webアクセスでは、DNSでIPアドレスを得てTCP接続を確立したあと、HTTPSならTLSハンドシェイクに進みます。TLSが終わってから、HTTPリクエストやレスポンスが暗号化された状態で流れます。
TLSハンドシェイクの概略図
sequenceDiagram participant Client as クライアント participant Server as サーバー Client->>Server: 使えるTLSバージョンと暗号方式を提示 Server-->>Client: 使う方式を選び、鍵交換材料を返す Server-->>Client: サーバー証明書で身元を示す Client->>Client: 証明書とホスト名を確認 Client->>Server: ハンドシェイク完了を確認 Note over Client,Server: 以後のHTTP通信は共通鍵で暗号化
TLS 1.3の流れを図で見る
sequenceDiagram autonumber participant Client as クライアント participant Server as サーバー Client->>Server: ClientHello(対応バージョン、暗号スイート、key_share、SNI) Server-->>Client: ServerHello(選んだ方式、key_share) Note over Client,Server: 共有秘密からハンドシェイク鍵を導出 Server-->>Client: EncryptedExtensions Server-->>Client: Certificate Server-->>Client: CertificateVerify Server-->>Client: Finished Client->>Client: 証明書とFinishedを検証 Client->>Server: Finished Note over Client,Server: アプリケーションデータ用の鍵を導出 Client->>Server: 暗号化されたHTTPリクエスト Server-->>Client: 暗号化されたHTTPレスポンス
TLS 1.3では、サーバー証明書や Finished など多くのハンドシェイクメッセージが、ServerHello 後に導出された鍵で暗号化されます。TLS 1.2以前より往復回数が少なく、古い危険な選択肢も整理されています。
ClientHelloで候補を提示する
最初に送る ClientHello には、クライアントが対応するTLSバージョン、暗号スイート、鍵交換グループ、ランダム値、拡張情報などが入ります。ブラウザでHTTPSサイトへアクセスするときは、SNIで接続したいホスト名も伝えます。
| 項目 | 役割 |
|---|---|
supported_versions | TLS 1.3など、対応するプロトコルバージョンを示す |
cipher_suites | 利用可能なAEAD暗号とハッシュの組み合わせを提示する |
key_share | ECDHEなどの鍵交換に必要な公開値を送る |
server_name | SNI。1つのIPで複数サイトを扱うサーバーが証明書を選ぶ材料になる |
alpn | HTTP/2やHTTP/1.1など、上位プロトコルの候補を伝える |
ServerHelloで方式を決める
サーバーは ClientHello の候補から、実際に使うTLSバージョン、暗号スイート、鍵交換グループを選び、ServerHello で返します。TLS 1.3では、この時点で双方が鍵交換の公開値を持つため、ECDHEの共有秘密を計算できます。
ここで作られる共有秘密そのものを通信に流すわけではありません。クライアントとサーバーがそれぞれ自分の秘密値と相手の公開値から同じ値を計算し、その値をHKDFで段階的に加工してハンドシェイク用の鍵やアプリケーションデータ用の鍵を作ります。
証明書でサーバーを確認する
鍵交換だけでは、相手が本当に目的のサーバーかは分かりません。そこでサーバーは証明書を送り、クライアントは証明書チェーン、期限、失効状態、ホスト名、信頼できる認証局にたどれるかを確認します。
CertificateVerify では、サーバーが証明書に対応する秘密鍵を持っていることを示す署名を送ります。証明書だけをコピーした攻撃者は秘密鍵を持たないため、この署名を正しく作れません。
Finishedで改ざんを検出する
Finished は、ここまでのハンドシェイクメッセージ全体を材料にした検証値です。クライアントとサーバーは、自分が見てきた会話の記録から Finished を計算し、相手から届いた値と一致するか確認します。
途中で攻撃者が暗号方式の候補や鍵交換材料を書き換えると、双方の会話記録が一致しなくなります。その結果、Finished の検証に失敗し、接続は成立しません。
TLS 1.2との違い
TLS 1.2でも目的は同じですが、典型的なフルハンドシェイクではTLS 1.3より多くのメッセージと往復が必要でした。また、TLS 1.2には古い暗号スイートや鍵交換方式を選べる余地があり、設定を誤ると安全性が落ちます。
| 観点 | TLS 1.3での変化 |
|---|---|
| 往復回数 | 通常の新規接続は1-RTTで完了しやすい |
| 鍵交換 | 前方秘匿性のあるECDHE系が前提になり、静的RSA鍵交換は使わない |
| 暗号方式 | AEAD暗号を中心に整理され、古い方式が削除された |
| ハンドシェイク保護 | ServerHello 後の多くのメッセージが暗号化される |
0-RTTは速いが再送に注意する
TLS 1.3には、以前の接続情報を使って早期データを送る0-RTTという仕組みがあります。遅延を減らせますが、早期データはリプレイ攻撃に注意が必要です。ログイン、購入、送金、状態変更のような処理を0-RTTで受け付ける設計は避けます。
開発時の切り分けポイント
HTTPS接続で失敗したときは、TCP接続、TLSハンドシェイク、HTTP処理を分けて考えます。TLSで止まる場合は、証明書、ホスト名、対応TLSバージョン、暗号スイート、プロキシやロードバランサーの設定を確認します。
| 症状 | 確認すること |
|---|---|
| 証明書エラー | 期限切れ、CN/SANのホスト名不一致、中間証明書の不足、信頼されないCA |
| protocol version | クライアントとサーバーで対応TLSバージョンが合っているか |
| handshake failure | 暗号スイート、鍵交換グループ、クライアント証明書要求の有無 |
| HTTPだけ失敗 | TLS成立後のルーティング、Hostヘッダー、アプリケーションログ |
まとめ
TLSハンドシェイクは、HTTPS通信の前に行われる暗号と信頼の準備です。ClientHello と ServerHello で方式を決め、ECDHEで共有秘密を作り、証明書と CertificateVerify でサーバーを確認し、Finished で会話全体の改ざんを検出します。ここまで成功して初めて、HTTPリクエストとレスポンスが安全な通信路で流れます。
詳細は RFC 8446(TLS 1.3)、RFC 5246(TLS 1.2) を参照してください。