TLS 1.3 と HTTPS ハンドシェイクの仕組み - 1-RTT・前方秘匿性・0-RTTを一次ソースで理解する

TLS 1.3 と HTTPS ハンドシェイクの仕組み - 1-RTT・前方秘匿性・0-RTTを一次ソースで理解する

作成日:
読了:16
更新日:
この記事を読む人におすすめPR / Amazonアソシエイト

当サイトは Amazon.co.jp を宣伝しリンクすることで紹介料を得る手段を提供する、Amazonアソシエイト・プログラムの参加者です。価格・在庫はリンク先の最新情報をご確認ください。

ブラウザのアドレスバーに鍵アイコンが表示され、URL が https:// で始まっているとき、その裏では通信を暗号化するための入念な準備が走っています。その準備の中核が TLS(Transport Layer Security)のハンドシェイクです。HTTPS とは、ざっくり言えば「HTTP を TLS の上に載せたもの」に他なりません。

この記事では、現在の主流である TLS 1.3 のハンドシェイクを、一次ソースである RFC 8446(The Transport Layer Security (TLS) Protocol Version 1.3)を軸に整理します。ClientHello から Finished までの流れ、ECDHE による鍵共有と前方秘匿性、旧仕様 TLS 1.2(RFC 5246)との違い、そして 0-RTT の注意点まで、Web 開発者・インフラ担当者向けに一通り押さえます。

NOTE

本記事はハンドシェイクの手順・数値・用語を RFC 8446(TLS 1.3)および RFC 5246(TLS 1.2)等の一次ソースを参照して整理しています。実装ごとの細部や、確定できない箇所については「未確認」と明示します。

TLS と HTTPS とは(まず結論)

TLS は、TCP コネクションの上で動く暗号化と認証のためのプロトコルです。役割は大きく3つに分けられます。

  • 機密性(暗号化): 通信内容を第三者に読まれないようにする
  • 完全性: 通信の途中で内容が改ざんされていないことを保証する
  • 認証: 接続先のサーバー(必要ならクライアント)が本物であることを、証明書で確かめる

TLS は TCP の上位に位置します。つまり、まず TCP の3ウェイハンドシェイクでコネクションを張り、その上で TLS ハンドシェイクを行い、さらにその上で HTTP のリクエスト・レスポンスをやり取りする、という三段構えです。名前解決からの一連の順序は「DNS で IP を引く -> TCP でつなぐ -> TLS で守る -> HTTP でやり取りする」と捉えると分かりやすいです。

なお、かつて使われた SSL は TLS の前身にあたる古い名称で、現在では設計上の欠陥から使われません。TLS 1.0 と TLS 1.1 も、RFC 8996(Deprecating TLS 1.0 and TLS 1.1、2021年)によって正式に非推奨とされ、Historic ステータスに移されました。今から新しく理解するなら、TLS 1.3(RFC 8446、2018年8月)と、まだ広く使われている TLS 1.2(RFC 5246、2008年8月)を押さえるのが実務的です。

TLS 1.3 ハンドシェイクの全体像

TLS 1.3 のフルハンドシェイクは、1往復(1-RTT)でアプリケーションデータの送信開始にこぎつけます。ここが後述する TLS 1.2 との大きな違いです。まず全体の流れをシーケンス図で見ます。

Loading diagram...

ポイントは、ServerHello の直後から通信が暗号化されることです。TLS 1.2 では証明書などが平文でやり取りされていましたが、TLS 1.3 では鍵共有が済んだ時点で導出した「ハンドシェイク鍵」を使い、EncryptedExtensions 以降のハンドシェイクメッセージも暗号化します。証明書の内容までのぞき見されにくくなっているわけです。

各メッセージの役割

図に出てきたメッセージを、RFC 8446 の定義に沿って整理します。

メッセージ送る側役割
ClientHelloクライアント乱数、対応バージョン、暗号スイート候補、鍵共有(key_share)を提示
ServerHelloサーバー乱数、選んだ暗号スイート、サーバー側の鍵共有を返す
EncryptedExtensionsサーバー暗号化された拡張情報(ALPN など)。ここから暗号化
CertificateRequestサーバークライアント認証が必要なときだけ送る(任意)
Certificateサーバーサーバー証明書(証明書チェーン)を提示
CertificateVerifyサーバーハンドシェイク全体に秘密鍵で署名し、証明書の持ち主であることを示す
Finished双方ここまでのハンドシェイク全体の MAC。改ざんがないことを確認

とくに理解しておきたいのが CertificateCertificateVerify の関係です。Certificate は「私はこの証明書を持っています」という主張にすぎず、それだけでは証明書を横取りした攻撃者でも送れてしまいます。そこで CertificateVerify で、証明書に対応する秘密鍵を実際に持っていることを署名によって証明します。この「公開鍵で検証し、秘密鍵の所有を示す」構図は、公開鍵暗号と電子署名の考え方そのものです。クライアントは証明書チェーンを信頼された認証局(CA)までたどって検証し、問題なければ Finished を返してハンドシェイクを完了します。

サーバーが送ってきた鍵共有がクライアントの想定と合わない場合には、サーバーは HelloRetryRequest を返し、使うべきグループを指定してクライアントに ClientHello をやり直させます。この場合は1往復ぶん増える点に注意します。

鍵共有と前方秘匿性(ECDHE)

TLS 1.3 のハンドシェイクで交換される情報は暗号化されますが、その暗号化に使う共有鍵は、どうやって安全に決めるのでしょうか。鍵そのものをネットワークに流したら、盗聴されて終わりです。

ここで使われるのが (EC)DHE、とくに楕円曲線を用いた ECDHE(Elliptic Curve Diffie-Hellman Ephemeral)です。Diffie-Hellman 鍵共有は、双方が「公開してよい値」だけを交換し合いながら、盗聴者には計算できない共有秘密を両者だけで導き出せる仕組みです。TLS 1.3 では、ClientHello の key_share と ServerHello の key_share がこの交換にあたります。

重要なのは末尾の E(Ephemeral、一時的)です。鍵共有に使う秘密の値をコネクションごとに使い捨てにすることで、前方秘匿性(Forward Secrecy)が得られます。前方秘匿性とは、「仮にサーバーの長期秘密鍵が将来漏れても、過去に記録された通信は復号できない」という性質です。攻撃者が通信を丸ごと保存しておき、後から鍵を盗んで解読する、という手口に対する備えになります。

RFC 8446 は、TLS 1.3 が公開鍵ベースの鍵交換において前方秘匿性を提供することを設計の柱の1つとしています。これは、鍵交換を静的な鍵に依存させないための重要な変更でもあります。

TLS 1.2 と TLS 1.3 の違い(対比表)

TLS 1.3 の設計は、TLS 1.2 の弱点や複雑さを整理し直したものです。主な違いを対比します。

観点TLS 1.2(RFC 5246)TLS 1.3(RFC 8446)
フルハンドシェイク2往復(2-RTT)1往復(1-RTT)
鍵交換の方式RSA 鍵交換や各種 DH など複数(EC)DHE のみ(静的 RSA / 静的 DH を廃止)
前方秘匿性方式次第(RSA 鍵交換では得られない)常に確保
証明書の暗号化平文でやり取りServerHello 以降は暗号化
暗号スイート鍵交換・認証・暗号・MAC を1つに束ねるAEAD 暗号とハッシュのみに簡素化
対称暗号CBC など非 AEAD も選べたAEAD のみ
圧縮あり廃止
セッション再開直後の送信不可(往復が必要)0-RTT で可能(条件つき)

とくに大きいのが 静的 RSA 鍵交換の廃止です。TLS 1.2 までは、クライアントが生成した「プリマスターシークレット」をサーバーの RSA 公開鍵で暗号化して送る方式が広く使われていました。しかしこの方式は前方秘匿性を持たず、サーバー秘密鍵が漏れれば過去の通信もすべて復号されてしまいます。TLS 1.3 では RFC 8446 が「静的 RSA と Diffie-Hellman の暗号スイートは削除された」と明記し、この方式を使えなくしました。

暗号スイートの表現も簡素になりました。TLS 1.2 では鍵交換・認証・共通鍵暗号・MAC の組み合わせを1つの長い名前で表していましたが、TLS 1.3 では鍵交換や署名アルゴリズムを別の拡張で扱い、暗号スイートはAEAD 暗号とハッシュ関数の組だけを示す形になりました。選択肢が減ったことで、危険な組み合わせを選んでしまう事故も起きにくくなっています。

参考までに、TLS 1.2 のフルハンドシェイクは次のような2往復の流れでした。

Loading diagram...

0-RTT と early data の注意

TLS 1.3 の目玉機能の1つが 0-RTT(early data)です。一度接続したことのあるサーバーに再接続する際、以前もらった鍵(後述の PSK)を使って、ハンドシェイクの完了を待たずに最初のリクエストを送れる仕組みです。往復のコストが減り、体感速度が上がります。

ただし便利さの裏にリスクがあります。RFC 8446 は 0-RTT データについて、次の2点を明確に警告しています。

  • 前方秘匿性がない: 0-RTT データはフルハンドシェイクで得る鍵とは別の鍵で守られ、前方秘匿性を持ちません。
  • 再送(リプレイ)を防げない: RFC 8446 は「コネクション間での非再送の保証はない」と述べています。攻撃者が 0-RTT データを記録して再送すると、サーバーが同じリクエストを2回受け取ってしまう可能性があります。

このため 0-RTT は、何度実行しても結果が変わらない操作(冪等な GET など)に限って使うのが基本です。決済や在庫の減算のように、2回実行されると困る操作に 0-RTT を許すべきではありません。アプリケーション側での対策や、サーバー側のアンチリプレイ機構と組み合わせて慎重に扱う必要があります。

WARNING

0-RTT を有効にする場合は、その経路で受け付けるリクエストが再送されても安全かを必ず確認してください。副作用のある操作を 0-RTT で受け付ける設定は、リプレイ攻撃の入り口になり得ます。

なお、この 0-RTT の考え方は、TLS 1.3 を輸送層に統合した QUIC にも受け継がれています。QUIC と HTTP/3 の全体像は HTTP/3 と QUIC の入門で扱っています。

セッション再開(PSK と NewSessionTicket)

同じサーバーに何度も接続する場面では、毎回フルハンドシェイクをやり直すのは無駄です。TLS 1.3 では、一度確立したセッションの情報を再利用するセッション再開が用意されています。

仕組みはこうです。ハンドシェイク完了後、サーバーは NewSessionTicket というメッセージでクライアントに「チケット」を渡します。クライアントは次回接続時、このチケットに対応する PSK(Pre-Shared Key、事前共有鍵)を使ってハンドシェイクを短縮できます。RFC 8446 では、この PSK ベースの再開が TLS 1.2 のセッション ID / セッションチケットの仕組みを置き換える形で定義されています。

セキュリティを重視するなら、PSK による再開時にも (EC)DHE を併用するモードを選ぶことで、再開したセッションにも前方秘匿性を持たせられます。前述の 0-RTT は、この PSK を土台にして「再接続の1発目を先に送る」機能である、と捉えると全体像がつながります。

実務での確認ポイント

TLS の挙動は、コマンドで実際に確認できます。まず、あるサーバーがどのバージョンや暗号スイートで接続してくるかは、OpenSSL の s_client で調べられます。

# TLS 1.3 での接続を試し、証明書やネゴシエーション結果を表示する
openssl s_client -connect example.com:443 -tls1_3
 
# curl で HTTPS のやり取りを詳細表示(使われた TLS バージョンが出る)
curl -v https://example.com/ 2>&1 | grep -i "SSL connection\|TLS"

openssl s_client の出力では、ProtocolTLSv1.3Cipher に選ばれた暗号スイート(TLS_AES_128_GCM_SHA256 など)が表示されます。証明書チェーン、検証結果(Verify return code: 0 (ok) なら検証成功)、有効期限も確認できます。実務では次のような点をチェックします。

  • 対応バージョン: TLS 1.2 以上のみを許可し、TLS 1.0 / 1.1 は無効にする(RFC 8996 に沿う)
  • 証明書の有効期限とチェーン: 期限切れや中間証明書の欠落は接続エラーの定番原因
  • 暗号スイート: AEAD ベースの安全なものに絞る
  • HTTPS の徹底: Cookie には Secure 属性を付け、平文の HTTP で送らない。詳しくは Cookie と SameSite の解説を参照

サーバー設定の総合的な評価には、外部の SSL テストサービスを使うのも有効です。証明書やプロトコル設定の弱点を洗い出せます。

まとめ

  • HTTPS は HTTP を TLS の上に載せたもの。TLS は TCP の上で機密性・完全性・認証を提供する
  • TLS 1.3(RFC 8446)はフルハンドシェイクが 1-RTT。ServerHello 以降は暗号化され、証明書ものぞき見されにくい
  • 鍵共有は (EC)DHE に一本化され、一時鍵によって前方秘匿性が常に確保される。TLS 1.2 の静的 RSA 鍵交換は廃止された
  • Certificate は証明書の提示、CertificateVerify は秘密鍵の所有証明。両者で「本物のサーバー」であることを確かめる
  • 0-RTT(early data)は速いが、前方秘匿性がなく再送を防げない。冪等な操作に限って慎重に使う
  • セッション再開は PSK と NewSessionTicket で実現。(EC)DHE 併用で再開時も前方秘匿性を持たせられる

TLS 1.3 の設計は、「速くする」ことと「安全側に倒す」ことを両立させた再整理と言えます。ハンドシェイクの各メッセージが何を保証しているのかが分かると、証明書エラーの原因調査や、サーバーの TLS 設定の勘所も自然と見えてきます。

参考リンク

HTTP/3 と QUIC 入門 - なぜ HTTP は TCP を捨てて UDP に移ったのか

HTTP/3 と QUIC 入門 - なぜ HTTP は TCP を捨てて UDP に移ったのか

19

HTTP/3 と、その土台となる新トランスポート QUIC を、一次ソースの RFC を軸に入門解説します。HTTP/2 が抱える TCP 上の Head-of-Line ブロッキング問題から出発し、QUIC が UDP の上でストリーム多重化・TLS 1.3 統合・0-RTT/1-RTT ハンドシェイク・Connection ID によるコネクションマイグレーションをどう実現するのかを直感的に整理。RFC 9000/9001/9002/9114/9204 の対応や、ブラウザ・サーバー・CDN の対応状況、curl や DevTools での確認方法、注意点とトレードオフまで Web 開発者向けにまとめます。

DNS の仕組み入門 - 名前解決・レコード種別・TTL とキャッシュを理解する

DNS の仕組み入門 - 名前解決・レコード種別・TTL とキャッシュを理解する

23

DNS(ドメインネームシステム)の名前解決の仕組みを、一次ソースである RFC を軸に整理します。ルート・TLD・権威サーバーの階層、再帰リゾルバとスタブリゾルバ、A/AAAA/CNAME/MX/NS/SOA/TXT/CAA/SRV/PTR などのレコード種別、再帰と反復の解決の流れ、TTL とキャッシュ、ネガティブキャッシュ(RFC 2308)、ポート53と UDP/TCP・EDNS0(RFC 6891)、DNSSEC(RFC 4033)、DoH(RFC 8484)/DoT(RFC 7858)まで、Web 開発者・インフラ担当者向けにまとめます。

SSH の仕組み - 鍵交換・ホスト鍵検証・公開鍵認証を一次ソースで理解する

SSH の仕組み - 鍵交換・ホスト鍵検証・公開鍵認証を一次ソースで理解する

18

リモートサーバー運用の土台である SSH プロトコルを、一次ソースの RFC 4251/4252/4253/4254(SSH-2 の Architecture / Authentication / Transport / Connection)を軸に整理します。TCP 接続からバージョン交換、KEXINIT による鍵交換と Diffie-Hellman、ホスト鍵の検証(known_hosts と TOFU の弱点)、公開鍵認証(authorized_keys と署名)、チャネルの多重化とポートフォワーディング(ssh -L / -R / -D)まで、その仕組みと OpenSSH での確認方法をインフラ担当者・開発者向けにまとめます。