TLSと耐量子暗号

2026年03月18日 水曜日


【この記事を書いた人】
山本 和彦

Haskellコミュニティでは、ネットワーク関連を担当。 4児の父であり、家庭では子供たちと、ジョギング、サッカー、スキー、釣り、クワガタ採集をして過ごす。

「TLSと耐量子暗号」のイメージ

TLSと耐量子暗号

量子コンピュータの実現を信じる人たちは、しきりとハーベスト攻撃(harvest now and decrypt later)の危険性を訴えています。ハーベスト攻撃とは、現在の暗号通信を保存しておき、後で解読可能になった時点で解読して、通信を盗み見るという行為です。

現在普及している公開鍵暗号は、RSAや(楕円曲線を含む)Diffie-Hellmanですが、強度の根拠となる素因数分解や離散対数問題は、ショアのアルゴリズムを使うと量子コンピュータで効率よく解読できるとされています。

量子コンピュータを使った暗号解読に対抗するために「耐量子暗号」が考案されました(間違いやすいのですが「量子暗号」とは別物です)。中でも NIST が標準化し、急速に普及し始めているのが ML-KEM (Module Lattice based Key Encapsulation Mechanism) です。この記事では、ある程度の暗号の知識を前提として、TLSとML-KEMの関係について説明します。

KEMとは何か

ML-KEMのMLは、モジュール格子問題(module lattice)を意味しています。僕は暗号の専門家ではないので、モジュール格子問題の説明は他の優れた記事にお任せします。ただ読者の方は、モジュール格子問題に対して、量子コンピュータを使った効率のよい解読アルゴリズムがまだ見付かってないことが分かれば十分です。

では、KEM(key encapsulation mechanism)とは何でしょうか? これは、公開鍵暗号を使って、共通鍵を共有するためのAPIのことです。僕はKEMを「RSAによる鍵配送」や「Diffie Hellmanによる鍵交換」の一般化と捉えています。

TLS 1.2でよく使われていたRSAによる鍵配送をおさらいしてみましょう。

  1. サーバは、サーバの証明書(Certificate)をクライアントに送ります。サーバの証明書の中には、サーバのRSA公開鍵が格納されています
  2. クライアントは、共通鍵暗号の共通鍵として乱数を生成し、サーバの公開鍵を使って暗号化(ClientKeyExchange)して、サーバに送ります
  3. サーバは、暗号文をサーバの秘密鍵で復号して、共通鍵を取り出します

現在では、この鍵配送方式は推奨されていません。なぜなら、証明書と公開鍵は静的であり、それに対応する秘密鍵も有効期間中はどこかに保存しておく必要があるからです。秘密鍵が将来漏洩すると、保存された暗号通信を解読されてしまいます。前方秘匿性を実現するには、公開鍵と秘密鍵は、必要に応じて生成し使い捨てなければなりません。

TLS 1.3 で使われている使い捨て楕円曲線Diffie Hellman(ECDHE)による鍵交換は、以下の手順を踏みます。

  1. クライアントは、公開鍵と秘密鍵を生成し、公開鍵(key_share拡張)をサーバに送ります。
  2. サーバも、公開鍵と秘密鍵を生成し、公開鍵(key_share拡張)をクライアントに送ります。また、サーバの秘密鍵とクライアントの公開鍵を使って、共通鍵を生成します
  3. クライアントは、クライアントの秘密鍵とサーバの公開鍵を使って、2)と同一の共通鍵を生成します

上記のRSAの鍵配送を拡張して、サーバは公開鍵と秘密鍵を生成して使い捨てるとしましょう。通信の向きが逆ですが、そこは無視することにします。すると、鍵交換と合わせて抽象化できそうです。なぜなら、どちらも一往復で済むからです。

鍵配送と鍵交換は、以下のように抽象化できます。暗号学の古式に則って、AliceとBobを登場させます。

  1. Aliceは、公開鍵と秘密鍵を生成します(generate)。そして、公開鍵を Bob へ送ります。
  2. Bobは、Aliceの公開鍵と何らかの方法を使って、「カプセル化データ」を生成し(encapsulate)、Alice へ送ります。また、このとき共通鍵も生成します
  • 鍵配送であれば、乱数を生成して共通鍵とし、Aliceの公開鍵で暗号化します。配送されるのは、共通鍵の暗号文です
  • 鍵交換であれば、まず公開鍵と秘密鍵を生成します。そしてBobの秘密鍵とAliceの公開鍵を使って、共通鍵を生成します。配送されるのは、Bobの公開鍵です
  1. Aliceは、そのデータを使って、共通鍵を取り出します(decapsulate)。
  • 鍵配送であれば、Aliceの秘密鍵で暗号文(カプセル化データ)を復号して、共通鍵を取り出します
  • 鍵交換であれば、Aliceの秘密鍵とBobの公開鍵(カプセル化データ)から、共通鍵を生成します

KEMとは、generate、encapsulate そして decapsulate という三点セットのAPIのことです。ML-KEMは、細かく言うと鍵配送に分類できます。経験談としては、ECDHEによる鍵交換もML-KEMによる鍵配送も、KEMというAPIを通じて実装すると、コードの見通しがよくなりました。

ハイブリッド鍵交換

IETFでは、ML-KEMを単体で標準化する(TLS 1.3のkey_share拡張で配送できるようにする)ことに対して、強い反対意見が出ています。将来、ML-KEMに弱点が見つかるかもしれませんし、ハイブリッド鍵交換があるのに、わざわざ単体で使うことにお墨付きを与えるべきではないという訳です。

では、ハイブリッド鍵交換とは何でしょうか? これは単純にECDHEの生成物とML-KEMの生成物を連結して送ると言う、とても簡単な方式のことです。具体的には、以下のようになります。

  1. クライアントは、ECDHEとML-KEMのそれぞれに対してgenerateします。2つの公開鍵を連結し、ClientHelloのkey_share拡張に入れて、サーバに送ります。2つの秘密鍵は、もちろん一時的に保存しておきます
  2. サーバは、key_shareの中身を分割して、ECDHEとML-KEMそれぞれの公開鍵を取り出します。そして、ECDHEとML-KEMのそれぞれに対してencapsulateし、カプセル化データを生成します。それらを連結して、ServerHelloのkey_share拡張に入れてクライアントへ送ります。また、サーバは生成された2つの共通鍵を連結して最終的な共通鍵とします
  3. クライアントは、key_shareの中身を分割して、ECDHEとML-KEMのカプセル化データとし、それぞれをdecapsulateします。こうして得られた2つの共通鍵を連結して、最終的な共通鍵とします

このように、共通鍵も単純に2つの鍵の連結です。一方が解読されても、他方が解読されなければ、安全であると言われています。現時点で、ハイブリッド鍵交換には、以下の3つがあります。

  • X25519MLKEM768 (ML-KEM-768とX25519を連結:順番が逆なのは歴史的経緯)
  • SecP256r1MLKEM768 (P-256とML-KEM-768を連結)
  • SecP384r1MLKEM1024 (P-384とML-KEM-1024を連結)

X25519、P-256、そして P-384 は、よく使われている ECDHE です。参考までに、それぞれのデータの大きさをまとめておきます。

公開鍵 カプセル化データ 共通鍵
X25519 32 32 32
P-256 65 65 32
P-384 97 97 48
ML-KEM-768 1184 1088 32
ML-KEM-1024 1568 1568 32

このように、長さは予め分かっている定数なので、連結されたデータを分割することは簡単です。

普及状況

OpenSSLや主要なブラウザは、すでにハイブリッド鍵交換に対応しています。クライアント側は、X25519MLKEM768とX25519の両方のkey_share拡張を送る実装が多いようです。サーバが前者を選ぶことを望んでいますが、サポートしてないサーバのために後者も送ります。X25519だけを送る場合と比べて、実に約40倍のデータ量になります。

OpenSSLのサーバは、たとえばクライアントからX25519のkey_shareだけが送られてきた場合でも、クライアントがX25519MLKEM768をサポートしていると主張しているなら、HelloRetryRequestを使ってX25519MLKEM768のkey_shareを送り直すことを要求できます。OpenSSLの実装者から直接教えてもらったときは、びっくりしました。

山本 和彦

2026年03月18日 水曜日

Haskellコミュニティでは、ネットワーク関連を担当。 4児の父であり、家庭では子供たちと、ジョギング、サッカー、スキー、釣り、クワガタ採集をして過ごす。

Related
関連記事