QUICの暗号化と鍵の導出について

QUIC, HTTP/3 関連記事


HTTP/3というかQUICの通信がどのように暗号化されているのか勉強がてら軽くまとめる。(執筆時点でQUIC-TLS Draft 19)

QUICでは、通信の多くが暗号化されています。アプリケーションデータはもちろんのこと、ACKや切断といった制御情報も暗号化されます。さらにパケット番号やフラグが含まれるQUICパケットのヘッダ部分も暗号化されています。

これらの通信の保護がどのように行われているか見ていきます。


先にQUICの概要についてざっとここらへんを見ていただけると良いかと思います。
asnokaze.hatenablog.com

目次

QUICのハンドシェイク

まずは、QUICのハンドシェイクを見ていきます。

TLS1.3のメッセージを流用しており、QUICの上でTLS1.3のメッセージをやりとりしています。
f:id:ASnoKaze:20190421222513p:plain
(引用 QUIC Security)

  • コネクションを開始するクライアントは、InitialパケットにCRYPTOフレームを格納し、ClientHelloのメッセージを送信します。(これはInitial Keysで暗号化されます)
  • もし、0-RTTでEarly Dataを送る場合は送信します。(これは、Early Data (0-RTT) Keysで暗号化されます)
  • サーバはClientHelloに対してInitialパケットにCRYPTOフレームを格納し、ServerHelloのメッセージを送信します。(これはInitial Keysで暗号化されます9
  • ServerHelloが送信できれば、Handshake Keysが導出できるので、サーバはHandshakeパケットにCRYPTOフレームを格納し、TLSハンドシェイクの残りを送信します(EncryptedExtensions, Certificate, CertificateVerify, Finished)。(これは、Handshake Keysで暗号化されます)
  • クライアントがHandshakeパケットを受け取ると、同じくFinishを送るためにHandshakeパケットにCRYPTOフレームを格納し送信します。

こうしてアプリケーションデータを暗号化するための、Application Data (1-RTT) Keysが導出できアプリケーションデータが送受信できます。

QUICでは、下記の4つの鍵が登場します。その鍵によって各パケットのペイロードが保護されます。上記図では、枠線の色ごとに使用する鍵が対応しています。

  • Initial Keys
  • Early Data (0-RTT) Keys
  • Handshake Keys
  • Application Data (1-RTT) Keys


TLS_AES_128_CCM_8_SHA256を除いてTLS1.3で定義される暗号スイートが利用できます。この暗号スイートを用いて暗号化及び(ハッシュ関数を用いて)鍵の導出が行われます

ショートパケットとロングパケット

パケットのペイロードの暗号化に先立ち、QUICパケットに関して簡単に復習します。

ハンドシェイク中のQUICパケットはすべてロングパケットです。アプリケーションデータはショートパケットで送信されます。

ハンドシェイク中に登場したようにロングパケットとしては、Initialパケット、Handshakeパケット、0-RTTパケットなどがあります。また、紹介しませんがRetryパケットといったものもあります。それぞれロングパケットヘッダ領域を持ちますが、それ以外はパケットごと固有になります。

例えば、Handshakeパケットは下記のようになります。
f:id:ASnoKaze:20190422101405p:plain

ペイロード領域に、TLSメッセージを含むCRYPTOフレームや、Ack情報を格納するACKフレームなどが含まれます。


アプリケーションデータやコネクションの制御情報を含むショートパケットは以下のようになっています。
f:id:ASnoKaze:20190422101518p:plain

ロングパケットに比べ一部の領域が省略されていることが分かります。

鍵の導出

Initial Keys

ClientHello, SeverHelloを運ぶInitialパケットを保護するInitial Keysは特殊であり、仕様に定義されるinitial_saltと送信先コネクションIDから導出されます。

具体的には下記の通りに、client_initial_secret とserver_initial_secret が導出されます。

   initial_salt = 0xef4fb0abb47470c41befcf8031334fae485e09a0
   initial_secret = HKDF-Extract(initial_salt,
                                 client_dst_connection_id)

   client_initial_secret = HKDF-Expand-Label(initial_secret,
                                             "client in", "",
                                             Hash.length)
   server_initial_secret = HKDF-Expand-Label(initial_secret,
                                             "server in", "",
                                             Hash.length)

saltはdraftバージョンで固有。今後変わる可能性があります。つまり、対応しているdraftバージョンでないと正しく復号できません。実装は、対応してない仕様を処理しようとして不具合を起こすことがありません。

少々古いですが下記に書いたとおりです。
asnokaze.hatenablog.com

そのほかの鍵の導出

それ以外の Early Data (0-RTT) Keys, Handshake Keys, Application Data (1-RTT) Keysは、TLS1.3の手順で導出されたSecretよりさらにそこから導出されます。

QUICのハンドシェイクではTLS1.3のメッセージをやり取りしています。TLS1.3の鍵導出は「RFC8446 7.1. Key Schedule」に書かれているとおりです。

下記はQUICにおける鍵スケジュールです。黄色いのはTLS1.3と同じですが、導出されたSecretからさらにQUICで使うSecretが導出されています。それが青い部分です。(図は、それぞれHKDF-ExtractとHKDF-Expand-Label)

f:id:ASnoKaze:20190421224549p:plain
(引用: QUIC Key Schedule)

QUICでは下記のそれぞれの鍵として

  • Early Data (0-RTT) Keys
  • Handshake Keys
  • Application Data (1-RTT) Keys

下記の秘密地をTLSのSecretより導出します

  • AEAD key (Label: quic key)
  • IV (Label: quic iv)
  • header protection key (Label: quic hp)

Application Data (1-RTT) Keysのみ、導出したSecretはKey Updateによって更にアップデートされていきます。

パケット保護

ペイロードの保護

各QUICパケットのペイロードは下記のように暗号化されます。

f:id:ASnoKaze:20190421225717p:plain
(引用 QUIC Security)

送信するパケットのパケット番号とIVの排他的論理和演算をとってNONCEとします。暗号化するパケットペイロードと、暗号化はしないが改ざんさてないことを確認するAADとしてパケットヘッダを入力します。あわせてNONCEと導出した鍵を入力します。

そうしてProtected Payloadを得ます。

復号する場合は、同様に求めたNONCE、鍵、暗号文を入力し平文を得ます。(実際には後述のパケットヘッダの復号を先に実施する必要があります。)

パケットヘッダの保護 (Header Protection)

QUICではペイロードのみならず、ロングパケット、ショートパケットともにケットヘッダの一部も保護されます。具体的にはパケット番号や鍵フェースなどです。

ヘッダパケットは下記のように、Protected Payloadより導出したMaskで保護されます。
f:id:ASnoKaze:20190421230951p:plain
(引用 QUIC Security)

  • Protected Payloadからサンプリングしたデータを、先に導出したHeader Protecion用の鍵でAES-ECB暗号化する形でMaskを計算します。
  • 得られたMaskを使って、ヘッダのフラグ部分とパケット番号部分の排他的論理和を取ります。

(サンプリングする箇所(サンプリングオフセット)はコネクションID長などから決定されます。)

こうしてヘッダ部分も保護されます。

復号する場合は、同様にProtected PayloadからMaskを取得しヘッダ部分を復元します。こうしてやっと、ペイロード部分の復号が可能になります。

Key Update

Key UpdateはTLS1.3とは異なり、パケットヘッダのKey Phaseビット(1bit)をトグルすることで、鍵のアップデートをピアに通知します。

しかし、この仕様は現在議論中で、新しくKEY READY bitを導入することも検討されています。

github.com

その他

実際にはパケットの順番などが入れ替わるため、再送処理や、それに伴う鍵の保持期間など細かい話がたくさんあります。。。

難しい。

この記事を書くにあたり大変参考になりました

ありがとうございます。