TLS ハンドシェイクとは

TLSハンドシェイクは、HTTPS通信を始める前に、クライアントとサーバーが通信相手を確かめ、同じ暗号鍵を作る手順です。
ここでは現在の中心であるTLS 1.3の通常のフルハンドシェイクを軸に説明します。実際のメッセージは、再接続、クライアント証明書、対応機能によって変わります。
証明書と鍵交換は役割が違う
最初に、混同しやすい2つを分けます。
| 仕組み | 主な役割 |
|---|---|
| サーバー証明書 | 接続先のサーバーが、対象ドメインの正当な相手か確認する |
| ECDHEなどの鍵交換 | クライアントとサーバーが、通信を暗号化する共通の秘密を作る |
証明書そのものが、Webページのデータを暗号化するわけではありません。サーバーは証明書に対応する秘密鍵を持っていることを署名で示し、鍵交換で作った秘密から通信用の鍵を導きます。
TLS 1.3の基本の流れ
sequenceDiagram
participant C as Client
participant S as Server
C->>S: ClientHello<br/>対応version・key_share・SNI・ALPN
S->>C: ServerHello<br/>version・暗号方式・key_share
Note over C,S: 共有秘密からhandshake鍵を導出
S->>C: EncryptedExtensions
S->>C: Certificate
S->>C: CertificateVerify
S->>C: Finished
C->>S: Finished
Note over C,S: Application Data
1. ClientHello
クライアントが、対応するTLSバージョン、暗号スイート、鍵交換用のkey_shareなどを送ります。
よく含まれる拡張機能は次のとおりです。
- SNI: 接続したいホスト名を伝える
- ALPN: HTTP/2の
h2など、TLSの上で使うプロトコル候補を伝える - supported_versions: 対応するTLSバージョンを伝える
通常のSNIは暗号化前に見える場合があります。ECH(Encrypted Client Hello)が利用できる接続では、ClientHelloの機密性を高められますが、利用可否はクライアント、DNS、サーバー構成に依存します。
2. ServerHelloと鍵の導出
サーバーがTLSバージョン、暗号スイート、鍵交換用のkey_shareを選びます。双方はECDHEによる共有秘密を計算し、そこからハンドシェイク用の鍵を導きます。
クライアントが送った鍵交換グループをサーバーが使えない場合は、HelloRetryRequestで別のkey_shareを求めることがあります。その場合は往復が追加されます。
3. サーバー証明書の確認
サーバーは、暗号化されたハンドシェイクメッセージとして証明書チェーンを送ります。クライアントは少なくとも次を確認します。
- URLのホスト名が証明書のSAN(Subject Alternative Name)に一致するか
- 現在時刻が証明書の有効期間内か
- 証明書チェーンの署名を検証でき、信頼する認証局へつながるか
- 証明書の用途やアルゴリズムが接続条件に合うか
失効確認の方法や失敗時の扱いは、クライアントや運用構成によって異なります。
証明書チェーンは、通常、サイトの証明書から中間認証局を経て、端末が信頼するルート認証局へつながります。サーバーが必要な中間証明書を送っていない、URLとSANが違う、端末時刻がずれている、といった問題でも検証は失敗します。
4. CertificateVerifyとFinished
CertificateVerifyは、サーバーが証明書に対応する秘密鍵を持つことを、ここまでのハンドシェイク内容への署名で示します。
Finishedは、ハンドシェイク内容が途中で改ざんされていないことを双方が確認するメッセージです。クライアントもFinishedを返したあと、通常のアプリケーションデータを送受信します。
一般的なTLS 1.3のフルハンドシェイクでは、クライアントがアプリケーションデータを送れるようになるまでのネットワーク往復は1 RTTが基本です。ただし、再試行、証明書取得、ネットワーク状態などで実際の待ち時間は変わります。
TLS 1.2との違い
TLS 1.2は現在も見かけますが、メッセージの組み合わせがTLS 1.3より多く、選んだ暗号スイートによって鍵交換方法も変わります。
| 観点 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 通常のフルハンドシェイク | 一般に2 RTT | 基本は1 RTT |
| 暗号スイート | 鍵交換・認証・暗号をまとめて表現 | 主にAEAD暗号とハッシュを表現 |
| 静的RSA鍵交換 | 構成によって利用されていた | 廃止 |
| サーバー証明書 | 暗号化前に送る | ServerHello後は暗号化して送る |
| 古い暗号方式 | 設定によって選択可能 | 多くを仕様から除外 |
TLS 1.2でもECDHEを使えば前方秘匿性を得られます。「TLS 1.2だから常に危険」とは限りませんが、安全な設定の選択肢が多く、運用ミスを避ける知識がより必要です。
再接続と0-RTT
一度接続した相手には、セッション再開を使ってハンドシェイクを短縮できる場合があります。TLS 1.3の0-RTTでは、条件が整えば最初の往復を待たずに早期データを送れます。
ただし、0-RTTの早期データには**再送攻撃(replay)**の危険があります。攻撃者が同じデータを再送すると、サーバー側で処理が重複する可能性があります。
そのため、0-RTTは次のように扱います。
- 情報取得など、再実行しても状態を変えない操作に限定する
- 購入、送金、登録、削除などの処理には使わない
- サーバー側の再送対策だけに依存せず、アプリケーションでも重複を考慮する
- 対応していない接続でも正しく動くようにする
0-RTTは「TLS 1.3なら常に使われる機能」ではありません。再開情報、クライアント、サーバー設定、アプリケーションの許可がそろった場合だけ利用されます。
ブラウザで確認する
Chrome DevToolsでは、Networkパネルの列設定からProtocolを表示すると、HTTP側でh2やh3が使われたか確認できます。TLSの詳細はブラウザやOSの表示範囲が異なります。
OpenSSLでは、接続先がTLS 1.3に対応しているかを確認できます。
openssl s_client -connect example.com:443 -servername example.com -tls1_3
出力では、接続先、証明書チェーン、検証結果、選ばれた暗号スイートを確認します。開発環境で検証エラーが出た場合も、安易に証明書検証を無効化せず、ホスト名、時刻、証明書チェーンを調べます。
ブラウザでは正常なのにコマンドや古い端末だけ失敗する場合、端末ごとの信頼ストア、対応TLSバージョン、SNIの指定を比較します。逆に、全クライアントで同時に失敗した場合は、証明書の期限、サーバーが返すチェーン、直前の設定変更を優先して確認します。
証明書の自動更新後も、実際に配信されている証明書と有効期限を外部から監視します。 更新処理の成功ログだけで、配信切り替えまで成功したとは判断しません。
まとめ
- TLSハンドシェイクは、相手の認証と通信用の鍵の確立を行う
- 証明書はサーバーを認証し、ECDHEなどの鍵交換が共有秘密を作る
- TLS 1.3では、ServerHello後のハンドシェイク内容が暗号化される
- 通常のTLS 1.3フルハンドシェイクは1 RTTが基本だが、条件で変わる
- 0-RTTは再送される可能性があり、状態を変える操作には向かない
参考リソース
- RFC 8446 - The Transport Layer Security (TLS) Protocol Version 1.3
- RFC 8446 Section 8 - 0-RTT and Anti-Replay
- RFC 5246 - The Transport Layer Security (TLS) Protocol Version 1.2
- RFC 6066 - TLS Extensions: SNI