
古典的なSSLのRSA鍵輸送を図でたどり、公開鍵と共通鍵の役割、いまのTLSへつながる疑問を整理します。

暗号化と認証⑥
共通鍵は、どうやって相手と共有するのか
SSLの教科書的な説明から、TLSの鍵交換と認証を追いかける
HTTPSでは、通信の内容が暗号化されます。
その仕組みを調べると、「公開鍵で共通鍵を暗号化して相手へ渡し、その後は共通鍵で通信する」という説明を見掛けます。
公開鍵と共通鍵が、一つの通信の中でどう働くのか。まずは、この流れをブラウザーとWebサーバーのやり取りとして追ってみます。
このシリーズでは、①から⑤まで、暗号化、鍵ペア、署名、RSAの計算、証明書を追ってきました。
ここからは、⑥から⑩の五回で、それらを実際のWebアクセスへつなげていきます。⑥では古典的なSSLの説明、⑦ではTLSの歴史と鍵共有、⑧ではOpenSSLによる接続の観察、⑨では鍵の作り分けと証明書・署名による認証を追います。⑩では、証明書の検証やC#・IPsecの例から、理解を広げます。
まず、SSL通信の流れを確認する

たとえば、ブラウザーから通販サイトへアクセスする場合を考えます。
ログインするならパスワードを送ります。買い物をするなら、カード番号などを送ることもあります。
そんな情報を、途中で誰かに読まれては困ります。しかも、暗号化する相手が偽サイトだったら、そこへ安全に届けても意味がありません。
そこで、接続先を確かめることと、やり取りする内容を守ることの両方が必要になります。
① サーバーから証明書を受け取り、公開鍵を確認する
ブラウザーがサイトへ接続すると、サーバーから証明書が送られてきます。
そこには、そのサイトの名前や公開鍵、発行者、有効期間などが入っています。
ブラウザーは、その証明書を信頼してよいか、アクセスしようとしている名前と合うか、有効期間内か、といったことを確かめます。
前回の⑤では、「その公開鍵は、本当に通信したい相手のものなのか」を追いました。
サーバー証明書、中間CA、ルートCA。そして、クライアントが持つ信頼の起点。
OpenSSLで証明書を取り出すと、以前はただの英字に見えたSubjectやIssuerにも、意味が見えてきました。
その確認を通して、ブラウザーは、接続先の名前に対応する公開鍵を得ます。
ここでは、この公開鍵がRSAの公開鍵だとします。対応するRSA秘密鍵は、サーバー側が保管しています。
② ブラウザーが、共通鍵の材料を作る
次に、ブラウザーが、通信に使う共通鍵を作るための秘密のデータを用意します。
この秘密データは48バイトで、内訳はバージョン情報2バイトと、暗号用途の乱数生成器で作った46バイトです。[1]
乱数なら何でもよいわけではありません。例えば.NETにも、一般用途の乱数と、暗号用途の乱数が用意されています。[2][3]
| .NETの乱数生成 | 用途 |
|---|---|
System.Random・Random.Shared | ゲーム、抽選、シミュレーションなど |
System.Security.Cryptography.RandomNumberGenerator | 暗号鍵の材料、秘密のトークンなど |
大切なのは、数字がばらばらに見えることだけではなく、攻撃者が値を予測できないことです。共通鍵の材料には、暗号用途の乱数生成器を使います。「安全に生成した」というのは、この予測しにくさを指しています。
つまり、「共通鍵の材料」の中心は乱数です。このデータはまだ完成した共通鍵ではなく、双方が通信の鍵を計算して作るための元になります。
ここでいう共通鍵は、サーバーだけが保管しているRSA秘密鍵とは別のものです。
シリーズ①でC#からAES-GCMを使ったときには、暗号化と復号に対応する共通鍵が必要でした。Webの通信でも、その準備が必要になります。
ブラウザーの側だけが材料を知っていても、サーバーは通信の鍵を作れません。かといって、材料をインターネットへそのまま流したら、途中で見ている人にも分かってしまいます。
では、どうやってサーバーにも渡せばよいのでしょう。
③ サーバーの公開鍵で、材料を暗号化して送る
ここで、証明書から取り出したRSA公開鍵を使います。
ブラウザーは、共通鍵の材料を、その公開鍵で暗号化してサーバーへ送ります。
シリーズ②で見たように、公開鍵で暗号化したものを取り出すには、対応する秘密鍵が必要です。
途中で通信を見ている人は、公開鍵も、暗号化された材料も見ることができます。でも、サーバーのRSA秘密鍵を持っていなければ、その材料を取り出せません。
鍵の材料を封筒に入れ、その封筒を相手の公開鍵で施錠して送る、と考えるとイメージしやすくなります。
④ サーバーが、秘密鍵で材料を取り出す
サーバーには、その公開鍵と対になるRSA秘密鍵があります。
その秘密鍵で、受け取った暗号文を復号します。すると、ブラウザーが用意した鍵の材料を取り出せます。
これで、ブラウザーとサーバーが、同じ秘密の材料を持てるようになります。
サーバーのRSA秘密鍵をブラウザーへ送ったわけではありません。ブラウザーが用意した材料を、サーバーだけが開けられるようにして渡しました。
⑤ 共通鍵を準備し、その鍵で通信する
同じ材料を使い、決められた計算をすれば、ブラウザーとサーバーは対応する通信の鍵を用意できます。
ここを簡略化すると、「ブラウザーとサーバーが同じ共通鍵を準備する」という説明になります。
実際には、同じ材料から送信方向ごとの鍵などを導出します。まずは、通信の両端が、そのやり取りを保護するための鍵を準備できた、と捉えます。
その後は、ログインの情報やWebページの内容を、共通鍵暗号で保護して送ります。
ブラウザーからサーバーへ送るデータも、サーバーからブラウザーへ返すデータも、この通信の鍵で守ります。
公開鍵で準備し、共通鍵で通信する
ここまでの説明で、証明書、公開鍵、共通鍵が、一つのWebアクセスの中につながりました。
| 使うもの | この説明での役割 |
|---|---|
| サーバー証明書 | 接続先の名前と公開鍵の対応を確かめる |
| サーバーのRSA公開鍵・秘密鍵 | 共通鍵の材料を、安全にサーバーへ渡す |
| 共通鍵暗号 | 準備した鍵で、実際の通信内容を保護する |
証明書の公開鍵で、Webページを最初から最後まで丸ごと暗号化し続ける、という流れではありません。
公開鍵暗号を使って、通信の鍵を準備する。その後、量の多い実際のデータは、共通鍵暗号で効率よく保護する。
公開鍵暗号で通信の鍵を準備し、共通鍵暗号で実際のデータを保護する、という役割分担です。
シリーズ①で扱った共通鍵の話と、②から④で見たRSA、⑤で見た証明書が、それぞれどこで働くのかも分かります。
ここまでなら、SSL通信の説明として筋が通っています。
ところが、この説明には但し書きがある
ただし、この説明には、大事な但し書きが付きます。
ここまでの流れは、RSA型の鍵交換を簡略化した説明です。TLS 1.3の証明書認証では、その公開鍵はサーバーの署名を検証するために使います。[4][1]
……え?
さっきまで、証明書の公開鍵で共通鍵の材料を暗号化して渡す、と説明していたはずです。
それが、実際には署名を確認するために使う?
署名の確認と、鍵の材料を暗号化して送ることは、シリーズ②で分けて考えた二つの使い方です。
省略していた細かい計算を足す、という話よりも、公開鍵が担当する仕事そのものが変わっているように見えます。
それなら、いまのブラウザーは、さっき説明した手順で通信しているのでしょうか。
せっかく一つにつながったところで、この但し書きに引っかかってしまいました。
いまの通信も、SSLと呼ばれている
ここで、SSLとTLSの名前も整理しておきます。
SSLの後継として、いまはTLSが使われている。そして、TLSによる通信も、慣用的に「SSL通信」と呼んでいる、というものです。
SSL通信、SSL証明書、SSL-VPN。こうした名前を見掛ける機会はありますが、その名前だけでは、実際に使うプロトコルの版や鍵交換方式までは分かりません。
SSLという名前が残っていることは分かりました。
でも、それだけでは、先ほどの但し書きの疑問は片付きません。
後継のTLSになったことで、名前に加えて、通信の手順も変わったのでしょうか。
公開鍵で鍵の材料を渡す説明は、いつの、どの方式の説明だったのでしょう。
ここを「いまはTLSです」で済ませると、仕組みが分かったというところまで戻れません。
では、いまの公開鍵は何をしているのか
知りたいのは、ブラウザーとサーバーの間で、実際に何が起きているかです。
公開鍵で鍵の材料を暗号化して送らないなら、双方はどうやって通信に使う秘密を用意するのでしょう。
そして、証明書の公開鍵で署名を確認するというのは、誰の、何に対する署名なのでしょう。
そこまで確かめれば、冒頭で確認した流れと、いまの通信との違いが見えてきそうです。違いが分かれば、なぜ手順を変えたのかも追えます。
普段はブラウザーやC#のライブラリに任せている、その接続の内側を見てみます。
ここから⑩まで、接続の入口からHTTPのやり取り、通信の終了まで、一つのTLS接続を追っていきます。
そして、「公開鍵で共通鍵を暗号化して渡す」という昔のSSLの説明と、今回の動きがどうつながるのかも、時代をさかのぼって確認します。
観察するのは、TLS 1.3でサーバー証明書を使って初めて接続する場合です。クライアント証明書を使う相互認証や、以前の接続を再開する場合は、手順の一部が変わります。
共通鍵を共有するために、何が必要だったのか
ここまでの疑問をまとめると、鍵配送問題に行き着きます。
共通鍵で暗号化すれば、通信の内容を守れます。でも、その鍵を、離れた相手とどうやって安全に共有するのでしょう。
まだ暗号化するための鍵がないのに、その鍵を安全に届けたい。通信を始める前に、まずここを解決する必要があります。
冒頭のRSA型の方式は、その答えの一つでした。証明書の公開鍵で秘密の材料を暗号化し、サーバーだけが持つ秘密鍵で取り出せるようにして渡す。
さらに、その材料から正しい通信の鍵を作り、手続きの最後に正しい確認値を返せることが、対応する秘密鍵を持つ相手だという確認にもつながります。RSA型の方式では、証明書の鍵ペアが、材料を受け渡す仕事と、相手を確かめる仕事を兼ねていました。[1]
ただ、この方法には気になるところがあります。
暗号化された材料を誰かが保存し、後からサーバーのRSA秘密鍵を手に入れたら、昔の材料も取り出せてしまう。通信していたときには秘密鍵を守れていても、後から失うことで、過去の通信まで影響を受けます。
そこで、今回見ている方式では、秘密の材料を運ぶ仕事を、一時的な鍵交換へ分けます。証明書の鍵ペアは、今回のやり取りに対する署名で、相手を確かめるために使います。
秘密の材料を安全に送る方法から、秘密の材料を送らずに双方で作る方法へ。
そして、秘密を共有できたことと、本物の相手と共有できたことを、別々の仕事として扱い、最後に結び付ける。
ここで気になるのは、いつ、何が変わったのかです。今の通信を追う前に、まず全体を見渡し、方式の名前と選択肢の変遷を整理しておきます。
次回
次回は「暗号化と認証⑦ ― SSL・TLSの歴史と、使い捨ての鍵共有」を取り上げます。
SSL・TLSが何を改善してきたのかを整理し、鍵共有方式の違いと変遷を見ます。その上で、使い捨ての鍵を使い、秘密そのものを送り合わずに双方で同じ秘密を作る仕組みを追います。
参考資料
- RFC 5246 — TLS 1.2:旧来のRSA鍵交換と、そのほかの方式。
- Microsoft Learn — System.Random:一般用途の乱数と、暗号用途でのRandomNumberGeneratorの使用。
- Microsoft Learn — RandomNumberGenerator:暗号用途の乱数生成。
- RFC 8446 — TLS 1.3:§2、§4.4、§6.1、§7。ハンドシェイク、CertificateVerify、Finished、鍵導出、接続の終了。