honai.me / blog

連載「入門 HTTP」(2) TLSとHTTP - TLSの概要

(Updated at )

更新履歴

  • 2022-05-26 資料のURLを修正

こんにちは。ほないです。 「入門 HTTP」( 動画とスライド ) を連載としてブログに投稿しています。 こちらは第2回の記事となります。

CAMPHOR- Day での発表の時は時間の関係で(自分の勉強不足もあり)3枚のスライドですっ飛ばしてしまったTLSの話ですが、 ブログではもう少し丁寧にまとめたいです。

と思って記事を書いていたら1回でまとめるには長くなってしまいました。 連載の投稿間隔が空くのも良くないなあと思い、2回に分割することになりました。

連載について

この連載全体については 第1回の記事 を参照してください。

本ブログ記事に掲載している画像の無断転載を禁じます。

第2回 TLSとHTTP - TLSの概要

今回筆者が想定している読者は、「AliceとBobの公開鍵暗号の話は知ってるけどTLSはよく知らない」というような方です。 筆者の個人的な意見ですが、TLSについての説明は、通信内容の暗号化ばかりに偏ってしまうことが多い気がしています。 しかし、サーバーの認証や完全性の検証も行わないと通信が保護されているとは言えません。 公開鍵基盤や前方秘匿性などについて簡単に知っておくだけでもTLSへの理解が深まると思います。

HTTP/1.1の仕様策定と並行して、TLS(Transport Layer Security)による通信の保護について策定されました。 https で始まるURLではTLSが使われています。

TLSとは通信のセキュリティを高めるための技術で、通信のなりすまし、盗聴、改ざんを防ぐことができます。

ブラウザの鍵マークでTLSが使われていることを示すスクリーンショット

TLSとTCP, HTTPの関係

TLSはアプリケーションプロトコルから独立しています。 つまり、上層のプロトコルは、TLSを利用しても、利用しない場合とプロトコルの規約を全く変える必要はなく、TLSは透過的に機能するということです。

前回の記事では、HTTP/1.xではTCPでリクエスト・レスポンスのテキストをエンコードしたバイト列を運んでいると説明しました。 TLSを利用してもHTTPの通信部分の規約を変更する必要は特にありません(ネゴシエーションなどを除く)。 つまりTCPの時と同じように、TLSにバイト列を運んでもらえばいいということです。

パケットの順序の保証やフロー制御、再送処理などは今まで通りTCPが担い、 認証、暗号化、完全性の検証をTLSが担って、 アプリケーション層に影響を与えることなく通信をセキュアにすることができます。

TLSとHTTPの関係を表す図

TLSプロトコルの大まかな処理の流れ

インターネットは基本的に通信内容はパブリックです。 IPパケットは各地の様々な組織によるルーターを経由して通信相手に届きます。 改ざんしたり盗聴したりすることは技術的にもかなり容易にできます。

TLSの主な目的は、サーバー(必要な場合はクライアント側も)を認証すること、 通信内容を暗号化すること、そして通信内容の完全性(改ざんされていないか)を保証することにあります。

以下の記事では、「公開鍵暗号」「共通鍵暗号(対称暗号)」「デジタル署名」などについては 大まかに理解していることを前提としています。

サーバー認証

サーバーの信頼性を保証する仕組みは、公開鍵基盤PKI: Public Key Infrastructure)と呼ばれるものです。

クライアントがTLSの通信を開始し、プロトコルの選択をしたあと、 サーバーは 証明書チェーン をクライアントに返します。

Chromeで適当な( https:// で始まる)サイトを開き、 鍵マークをクリックして「証明書」をクリックすると、そのサイトの証明書チェーンがわかりやすいUIで表示されます。

google.comの証明書
サーバー証明書
Google Trust servicesの証明書
中間証明書
GlobalSignの証明書
ルート証明書

1枚目がサーバ証明書で、Google LLC(と *.google.com に対する)証明書です。 この証明書はGoogle Trust Servicesという組織によって発行されていることがわかります。 証明書には発行者のデジタル署名がついています。Google Trust Servicesの証明書(公開鍵)を取得すれば、この証明書を検証できます。

2枚目がGoogle Trust Servicesの証明書です。 これにはGlobal Signという組織によって署名されていることがわかります。 同様にGlobal Signの公開鍵によってこの署名を検証します。

3枚目はGlobal Signの証明書ですが、この証明書は発行者と主体者が同じになっています。 このような証明書は「ルート証明書」と呼ばれます。 GlobalSignは「ルート認証局」と呼ばれる組織の1つです。ルート証明書自体の信頼性の保障は、公開鍵による検証だけではできず、別の仕組みが必要です(ルート証明書自体は単なる自己署名証明書であるからです)。

ブラウザやOSにはあらかじめ信頼できるルート認証局(とその公開鍵)のリストがインストールされています。 クライアントは、サーバ証明書、中間証明書を順に検証していき、 最終的にインストール済みのルート認証局による署名がでてくれば、その証明書の連なりを「信頼」できるものとして扱い、 通信しようとしているサーバーが何者かによってなりすまされていないことを確認します。

鍵交換

TLSでは基本的に通信内容(Application Data)は共通鍵暗号で暗号化されます。 公開鍵暗号あるいは鍵交換アルゴリズムを用いて、通信のための共通鍵をサーバーとクライアントが共有できるようにし、その共通鍵で実際の通信内容を暗号化します。

このように公開鍵(鍵交換アルゴリズム)と共通鍵を使い分けている理由は、高速化/計算量の削減のためです。 最初からすべて公開鍵暗号でやり取りするとプロトコルがシンプルになりそうなものですが、 公開鍵による暗号化/復号は一般に計算量が多く、スループットがかなり小さいので、実現不可能というわけです。

暗号化方式によるスループットの違い

実現不可能と言われてもイメージがわかないと思うので、検証してみます。 参考文献 の「Real World HTTP」のサポートリポジトリ oreilly-japan/real-world-http に、書籍中でも紹介してある比較用のGolangベンチマークコードが公開されています。 書籍中にも著者の渋川さんによるテスト結果が掲載されていますが、せっかくなので手元で試してみました。

128バイトのデータをRSA(公開鍵暗号)、AES(公開鍵暗号)で暗号化・復号するのにかかる時間とスループットは次のようになりました(単位に注意)。

環境: Surface Laptop 3, AMD Ryzen 5 (2.10GHz), Golang version 1.14 windows/amd64, 3回測定の平均

暗号化方式 モード 時間 スループット
RSA 暗号化 66.1 ms 14.8 Mbps
RSA 復号 1691 ms 591 kbps
AES 暗号化 198 ns 4.81 Gbps
AES 複合 203 ns 4.70 Gbps

もしもRSAでデータも全て暗号化していたら、 インターネットの転送速度のボトルネックがTLSになってしまうというのがよくわかると思います。

前方秘匿性

鍵交換を(例えばRSAなどの)公開鍵暗号で行う場合、手順はとてもシンプルで、クライアントがプリマスターシークレットという乱数を生成し、サーバーの公開鍵で暗号化して送信します。これが共通鍵(の最重要な素)となります。

この方法はシンプルな反面、サーバーの秘密鍵が漏洩した場合、 通信内容を事前にログしておけば、 過去に遡ってそのサーバーとあらゆるクライアントの通信内容を解読することができます。

秘密鍵が漏洩するなんてまずないでしょうと思われるかもしれませんが、 例えば法的な力によって捜査機関に秘密鍵が渡ってしまうことは十分あり得るケースです。

他にも、現在使われているアルゴリズムとbit長(例えばRSA 2048bit)が、 コンピュータ技術の進化によってサーバーの秘密鍵を解読できるようになるかもしれません。 その時まであるサーバーの通信ログをずっと保存しておけば、全ての通信を解読できてしまいます。

この問題に対処できるのが、鍵交換専用のアルゴリズムです。 これは接続毎にクライアントとサーバーでそれぞれランダムに鍵を生成し、 数学的に逆方向の計算が困難な計算を施した上で数値を交換し、 クライアントとサーバーで同じ鍵を得るというものです。

サーバーの秘密鍵を使わないので、仮に通信のログがあっても、通信ごとに別々の鍵の解読が必要になり、 公開鍵による鍵交換の時よりもハードルが上がります。

このように、将来に渡って通信内容の秘匿性が保たれることを前方秘匿性PFS: Perfect Forward Security)といいます。

暗号化と完全性の検証

共通鍵の交換ができたら、アプリケーションデータを暗号化できます。 また、クライアントとサーバーで共有したハッシュ鍵を利用してメッセージのハッシュ値を計算し一緒に送ることで、改ざん検出もすることができます。

以上がTLSによる通信の保護の基本的な流れになります。

TLSの歴史

HTTP/1.1 が仕様としてまとまり公開されたのは1997年ですが、 TLSも同じように長い歴史があります。

  • 1995年 SSL 3.0 (NetScape社)
  • 1999年 TLS 1.0 (RFC 2246)
  • 2006年 TLS 1.1 (RFC 4346)
  • 2008年 TLS 1.2 (RFC 5246)
  • 2018年 TLS 1.3 (RFC 8446)

NetScape社が1995年にリリースしたSSL 3.0が現在のTLSの基本設計となっています。 SSLがNetScapeからIETFに移管され、名前が変わりTLS 1.0がリリースされたのが1999年です。

現在メインで使われているのはTLS 1.2(2008年〜)とTLS 1.3(2018年〜)です。 TLS 1.0~1.2の間は、古くなったり安全でなくなったりした暗号スイート/鍵のbit長を無効化したりと、 比較的マイナーなバージョンアップでした(TLS拡張によって機能が追加されることはありました)。

しかし、1.3はハンドシェイクなどを含め変更点が多くあり、 メジャーアップデートのような内容になりました。

まとめ

TLSは安全でない通信路において、通信相手の認証と通信の機密性、完全性を確保するためのプロトコルです。 次回はもう少し具体的に、TCP・TLS・HTTPがどのようにかかわって通信を行うのか解説したいと思います。

参考文献

  • 渋川よしき (2020)『Real World HTTP 第2版』オライリー・ジャパン
  • Ristić, Ivan (2017)『プロフェッショナルSSL/TLS』(齋藤孝道 監訳) ラムダノート出版