SSL/TLS(SSL3.0~TLS1.2)のハンドシェイクを復習する


TLS1.3がRFC8446 (Google翻訳) として発出されたというご時世に今さらという気もしますが、思うところあってSSL3.0, TLS1.0~1.2の4プロトコルで共通の、暗号通信開始までのハンドシェイクを解説してみます。
ちなみにSSL2.0、QUIC、TLS1.3は、これとは全く別物です。

ハンドシェイクの基本

SSL/TLSハンドシェイクの基本は以下の表です。斜体文字の項目は、送信されない場合もあるということです。

クライアント発 サーバ発 備考
1 HelloRequest 「ClientHelloを送信せよ」
2 ClientHello
3 ServerHello
4 ServerCertificate
5 ServerKeyExchange
6 CertificateRequest 「ClientCertificateを送信せよ」
7 ServerHelloDone
8 ClientCertificate
9 ClientKeyExchange
10 CertificateVerify
11 ChangeCipherSpec 「暗号通信に移行する」
12 Finished 暗号文
13 ChangeCipherSpec 「暗号通信に移行する」
14 Finished 暗号文
15 ApplicationData ApplicationData 暗号文

以下順を追って説明します。

  1. HelloRequest
    相手にClientHelloを送信するよう促すメッセージです。送信しなくても構いません。

  2. ClientHello

  3. ServerHello
    ClientHelloとServerHelloは、TLSのひとつめの肝です。後ほど説明します。

  4. ServerCertificate
    サーバ証明書を送信します。中間CA証明書なども、ここで送ります。

  5. ServerKeyExchange
    鍵交換メッセージその1です。鍵交換はTLSのふたつめの肝で、これも後ほど説明します。

  6. CertificateRequest
    クライアント証明書を送信するように促すメッセージです。クライアント証明書が必要な場合に送信します。何そのクライアント証明書って?と思った方は読み飛ばして構いません。

  7. ServerHelloDone
    サーバからの送信終了を示すエンドマークです。

  8. ClientCertificate
    クライアント証明書です。サーバからCertificateRequest(上記6.)が来た場合に送信します。

  9. ClientKeyExchange
    鍵交換メッセージその2です。これも別途説明します。

  10. CertificateVerify
    クライアント証明書(ClientCertificate、上記8.)送った場合に送ります。クライアントが正しい秘密鍵を持っていることを証明するための署名です。

  11. ChangeCipherSpec
    無暗号通信の終了を示すエンドマークです。これより後の送信は暗号になります。

  12. Finished
    ハンドシェイクの終了です。
    今まで無暗号で送られてきたハンドシェイクメッセージ全体をハッシュし、その結果を送信します。この値を受信側(サーバ側)で検証することで、無暗号部分を中間者攻撃で改ざんされた場合に見破るというものです。

  13. ChangeCipherSpec
    無暗号通信の終了を示すエンドマークです。これより後の送信は暗号になります。

  14. Finished
    上記12. のFinishedと同じものですが、サーバ側から送信します。
    この値を受信側(クライアント側)で検証することで、無暗号部分を中間者に改ざんされた場合に見破ります。

  15. Application Data
    データ本文を暗号化して送受信します。

ClientHelloとServerHello

ClientHelloとServerHelloでは、初期乱数、プロトコルバージョン、SessionID、CipherSuite(暗号方式)、CompressionMethod(圧縮方式)、Extensions(拡張)の6要素を交換します。
また、SSL2.0との互換性を取るために、最初のClientHelloだけはSSL2.0形式のCLIENT-HELLOで、ServerHello以降はSSL3.0~TLS1.2形式という、ちょっと変則的なセッションが許容されています。

初期乱数(ClientHello初期乱数、ServerHello初期乱数)は、4バイトの時刻値と28バイトの乱数値を組み合わせた32バイトの値です。ただしSSL2.0互換CLIENT-HELLOの場合は、16~32バイトの乱数値です。
プロトコルバージョンは、要はSSL3.0かTLS1.0か1.1か1.2かということです。ClientHelloで提案値を送信し、ServerHelloで確定値を返します。
SessionIDは、これがクライアント値とサーバ値で一致すれば、双方にキャッシュされた計算済みパラメータを利用して、ハンドシェイク手順をすっとばしてChangeCipherSpecまで進めるというものです。
CipherSuiteは暗号方式を指定します。ClientHelloで候補値(複数可)を提示し、ServerHelloで確定値(1つ)を返します。
CompressionMethodは圧縮方式を指定します。これもClientHelloで候補値(複数可)を提示し、ServerHelloで確定値(1つ)を返しますが、2012年にCRIME脆弱性が発見されましたので、ここは「NULL」(無圧縮)一択を指定して下さい。SSL2.0形式のClientHelloではCompressionMethodは指定できません。
拡張もClientHelloで候補値(ゼロ個、複数可)を提示します。採用された場合はServerHelloで返信します。不採用の場合は何も返しません。SSL2.0形式のClientHelloでは拡張は指定できません。

このように、ClientHelloとServerHelloは原則的に、クライアント側で候補を提示、サーバ側で採否決定という流れになっています。

鍵交換

SSL/TLSではデータ本文の暗号方式は共有鍵暗号ですが、そこで使う共有鍵は、セッションごとに一時的に作る「使い捨て」です。使い捨て共有鍵はクライアントとサーバで同じ値を持つ必要があるわけで、これを実現する処理を鍵交換または鍵共有といいます。
問題はセキュアな鍵交換をどうやって実現するかという話で、無暗号でそのまま相手に送ったのでは盗聴されてしまいますので、何らかの暗号的な方法を使うことになります。
今のところSSL/TLSの世界でメジャーな鍵交換方式はRSA, DHE, ECDHEの3種類といったところです。マイナーなものはいろいろあります。

RSAは、SSL/TLSで伝統的に広く使われてきた方式です。
クライアントで46バイトの乱数(これがpre_master_secret)を生成し、RSA公開鍵(サーバ証明書に含まれる)で暗号化してClientKeyExchangeで送信します。サーバ側ではRSA秘密鍵を使って解読します。
ServerKeyExchangeは使いません。

DHE (Ephemeral Diffie-Hellman) は、一時鍵 (Ephemeral Key) を利用したDiffie-Hellman鍵交換です。方式自体は昔からありますが、最近注目されています。
下表のようにServerKeyExchangeとClientKeyExchangeの2回の通信でDiffie-Hellman鍵交換を行います。

クライアント サーバ
共通値(g, p)の作成
サーバ秘密鍵と公開鍵の作成
ServerKeyExchange 共通値(g, p)、サーバ公開鍵の送信
共通値(g, p)を基にクライアント秘密鍵、公開鍵の作成
ClientKeyExchange クライアント公開鍵の送信
共有秘密値の算出 共有秘密値の算出

ECDHE は、楕円関数Diffie-Hellman鍵交換です。DHEと同じく一時鍵 (Ephemeral Key) を使います。

鍵交換で共有した共有秘密は、pre_master_secretという扱いになります。ここから実際の暗号通信に使う共有秘密鍵を算出するのにもう二手間ほど必要で、それを次に説明します。

共有秘密鍵の算出

ServerHello~ClientKeyExchangeの通信過程で、クライアントとサーバの両方にClientHello初期乱数ServerHello初期乱数pre_master_secretの3つの乱数が手に入ったことになります。ここからmaster_secret(48バイト)を算出し、そこからさらにkey_blockを計算します。key_blockは理論上無限長の疑似乱数列で、必要な長さだけ計算し、そこからアプリケーションデータの暗号通信に使う共有鍵を取り出します。

ClientHello初期乱数、ServerHello初期乱数、pre_master_secret
 ↓
master_secret
 ↓
key_block
 ↓
暗号通信に使う共有鍵

この順序を覚えてください。

暗号通信

ハンドシェイク表の中のFinishedとApplication Dataが暗号通信になります。
暗号通信では、無暗号状態のデータにまずMAC(メッセージ認証コード)を付与します。受信側でこれを検証することで中間者攻撃による改ざんを見破るというものです。次に共通鍵暗号(例:AES)で暗号化するわけですが、ブロック暗号の場合は何らかの暗号利用モード(例:CBC)を併用します。
MACや暗号化に使う鍵は、key_blockから必要なバイト数を切り取って使います。このとき、MACの秘密鍵と暗号化の秘密鍵は別の値を使います。また、上り(クライアント→サーバ)方向の秘密鍵と下り(サーバ→クライアント方向)の秘密鍵も別の値を使います。

Q&A

Q. めんどくさい。これ本当に覚える必要あるのか。
A. はい(断言)。

Q. TLS False Startとは何か。また、それはセキュアなのか。
A. クライアント、サーバの双方がFinishedを送受信し終えてからApplicationDataの送受信へと進むわけですが、実際はちょっとフライングして、Finishedを送信し終えたら相手方からのFinished到着を待たずにApplicationDataの送信を開始しても通常は大丈夫です。この処理をFalse Startと呼びます。
厳密にいうとセキュアではありません。
相手からのFinishedを検証していない段階でApplicationDataの送信が始まってしまうため、中間者攻撃でハンドシェイク部分を改ざんされた場合、Finishedを検証すれば見破れるはずの攻撃を見破れず、データの先頭のほうが中間者の手に渡ってしまう可能性があります。

Q. 再ネゴシエーションとは何か。また、それはセキュアなのか。
A. 全然セキュアではありません。
暗号通信が確立した状態で、ハンドシェイク手順をもう一度取り交わすことを再ネゴシエーションと呼びます。効果としてはセッションを維持したまま暗号方式を変更できるということですが、2009年に中間者攻撃に対する脆弱性 (CVE-2009-3555) が発見されています。
そもそも何のためにこんな機能があるのかという話ですが、昔は必要だったのです。
20世紀に米国政府が強力な暗号製品を輸出規制していた時代、まずは輸出対応暗号(40bitまたは56bit)でSSL接続を確立し、その状態でクライアントはサーバ証明書の内容を見て、条件を満たせばSSL接続を維持したまま128bitに移行する、ということが行われていた時代があります。なんのこっちゃという話ですが、これはServer Gated Cryptography (SGC) と呼ばれ、こうすれば米国政府の輸出規制をクリアできた時代があったのです。
しかし、この規制はとっくに廃止されましたので、今さら再ネゴシエーションが必要になるのは、かなりレアな状況に限られると思います。何らかの事情で使う場合は、renegotiation_info拡張 RFC5746 (Google翻訳) を併用するようにして下さい。

Q. なぜ今DHEとECDHEが注目株なのか。
A. 傍受した暗号通信を未解読のまま長期保存し、将来何らかの方法で秘密鍵が手に入ったらそれを利用して解読するという攻撃があります。すごく金と時間のかかる方法ですが、国家情報機関ならばそこまでやるというのがスノーデン氏がもたらした情報から得られる結論です。
DHEやECDHEであれば、もし警察がサーバのRSA秘密鍵を押収したとしても、それだけで暗号通信の解読はできません。解読に必要なのはDiffie-Hellman秘密鍵ですが、それは通信の都度作成し、終わったらすぐに消去する「使い捨て」のデータですから、警察が来たときにはもう現物は消え去っており、押収はできないというわけです。これをForward Secrecyといいます。

Q. CBCは死んだのか。
A. CBCに対する中間者攻撃であるパディング・オラクル攻撃(と、その応用であるPOODLE)や選択平文攻撃(と、その応用であるBEAST)が相次いで知られるようになったのでCBCに注目が集まるわけですが、根本的な問題はTLSの設計にあります。
上記「暗号通信」の項で説明したとおり、TLSではMACを付与してから暗号化します(MAC-then-Encrypt、略してMtE)。実はこれが諸悪の根源であり、逆、つまりEncrypt-then-MAC (EtM) であれば、受信側はCBC処理を開始するまでもなくMAC検証の段階で中間者攻撃による改ざんを見破ることができたはずだったのです。
そんなわけで、TLS「Encrypt-then-MAC拡張」がRFC7366 (Google翻訳) として定義されました。2014年の話です。SSL2.0の制定から20年も経って今さらそれを言うかという気がしますが、世の中そんなものです。
もっとも世の中の趨勢はCBC-EtMではなく、暗号利用モードCCMやGCMを使う方向へと進んでいるようです。CCMやGCMは「認証つき暗号」、つまりMACに相当する機能が暗号利用モード自身に組み込まれているということです。それならMACを廃止してもよさそうなものですが、まあ昔の名残というか、MACも残っています。また、CCMもGCMもTLS1.1以前では使えません。
ここで当初の質問(CBCは死んだのか)への回答に戻ると、POODLEにしてもBEASTにしても個別の対策が用意されていますので、今はまだCBCが死んだとは言い切れません。それに将来的にも、もし万が一何らかの理由でCCMとGCMがコケてしまったら、AES-CBC-EtMが華麗に世界を席巻する可能性すら残っています。

参考文献

The SSL Protocol (SSL2.0)
https://tools.ietf.org/html/draft-hickman-netscape-ssl-00 (Google翻訳)

The Secure Sockets Layer (SSL) Protocol Version 3.0
https://tools.ietf.org/html/rfc6101 (Google翻訳)

The TLS Protocol Version 1.0
https://tools.ietf.org/html/rfc2246 (Google翻訳)

The Transport Layer Security (TLS) Protocol Version 1.1
https://tools.ietf.org/html/rfc4346 (Google翻訳)

The Transport Layer Security (TLS) Protocol Version 1.2
https://tools.ietf.org/html/rfc5246 (Google翻訳)

Transport Layer Security (TLS) Renegotiation Indication Extension
https://tools.ietf.org/html/rfc5746 (Google翻訳)

Encrypt-then-MAC for Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)
https://tools.ietf.org/html/rfc7366 (Google翻訳)