この記事の結論TLSハンドシェイクは、通信相手の確認と暗号鍵の合意を行う手続きです。TLS 1.3では、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_versionsTLS 1.3など、対応するプロトコルバージョンを示す
cipher_suites利用可能なAEAD暗号とハッシュの組み合わせを提示する
key_shareECDHEなどの鍵交換に必要な公開値を送る
server_nameSNI。1つのIPで複数サイトを扱うサーバーが証明書を選ぶ材料になる
alpnHTTP/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) を参照してください。