鍵合意 — Diffie–Hellman を手計算する
盗聴されている経路の上で共通鍵を作る Diffie–Hellman 鍵合意を、小さな素数で手計算して確かめる。ECDHE と前方秘匿性まで。
盗聴されている経路の上で、共通鍵を作る
第 2 章の宿題を思い出してください。本文を守る共通鍵暗号は速いが、最初の共通鍵をどう共有するかが問題でした。しかも前提は厳しく、ブラウザとサーバの間の経路は盗聴されているかもしれません。「盗聴されている経路の上で、盗聴者には分からない共通の秘密を作る」— 一見不可能に聞こえるこの問題を解くのが、Diffie–Hellman(DH)鍵合意です。
この章の目標はただ 1 つ、これを自分の手で計算して納得することです。必要な数学は「べき乗」と「割り算の余り(mod)」だけ。17 mod 5 = 2(17 を 5 で割った余りは 2)が分かれば準備完了です。
手順は 4 ステップ
あなたとサーバの 2 人で鍵を作ります。まず 2 人は、公開の場で 2 つの数を決めます — 素数 p と、その相棒 g。ここでは手計算できるように p = 23, g = 5 とします(実物は p が数百桁になるだけで、手順は同じです)。
a を選ぶ(例: a = 6)。サーバも秘密の数 b を選ぶ(例: b = 15)。これは誰にも送らないA = ga mod p = 56 mod 23 = 8 を送る。サーバは B = 515 mod 23 = 19 を送る。この 2 つは盗聴されて構わないs = Ba mod p = 196 mod 23 = 2。サーバ: s = Ab mod p = 815 mod 23 = 2gab mod p を計算しているので、必ず一致する。以降はこの値から作った共通鍵で高速に暗号化する大きなべき乗にひるむ必要はありません。2 乗を繰り返して、そのつど mod を取るのがコツです。たとえば 56 mod 23 は、52 = 25 ≡ 2 → 54 = (52)2 ≡ 4 → 56 = 54 × 52 ≡ 4 × 2 = 8。途中の数が 23 未満に保たれるので、暗算でも追えます。
経路を流れるのは公開値 A と B だけ。秘密の値 a, b と共有秘密 s は、それぞれの手元から一度も外に出ません。
小問 3-1 — 鍵合意を自分の手で回す
公開パラメータは p = 23, g = 5 とします。本文の手順どおり、紙に途中経過を書きながら計算してください。
Q1. ウォーミングアップ: 53 mod 23 はいくつですか。
5³ = 125。125 ÷ 23 = 5 余り 10 なので、答えは 10 です。mod は「割った余り」— この 1 個の演算だけで、この章の計算はすべて進みます。
Q2. あなたの秘密の値を a = 6 とします。相手に送る公開値 A = 56 mod 23 はいくつですか。(ヒント: 52 mod 23 から積み上げる)
5² = 25 ≡ 2、5⁴ = (5²)² ≡ 2² = 4、5⁶ = 5⁴ × 5² ≡ 4 × 2 = 8。答えは 8 です。大きなべき乗も、2 乗を繰り返して mod を取り続ければ、値が 23 未満に保たれたまま計算できます。
Q3. 相手から公開値 B = 19 が届きました。あなたの秘密の値 a = 6 で計算する共有秘密 s = 196 mod 23 はいくつですか。(ヒント: 19 ≡ −4 (mod 23) を使うと速い)
19 ≡ −4 なので 19⁶ ≡ (−4)⁶ = 4⁶。4³ = 64 ≡ 18、4⁶ = (4³)² ≡ 18² = 324 ≡ 2。共有秘密は 2 です。相手も自分の秘密の値 b = 15 で 8¹⁵ mod 23 = 2 を得ます。別々に計算したのに同じ値 — これが鍵合意です。
シミュレータで確かめる
下のシミュレータは p = 23, g = 5 の鍵合意を再現します。あなたの秘密 a とサーバの秘密 b を動かして、両者が別々に計算した共有秘密が必ず一致すること、そして盗聴者に見える欄には秘密が現れないことを確かめてください。
シミュレータの動作概要(テキスト版): a と b のスライダーを動かすと、公開値 A = ga mod p と B = gb mod p、およびあなた側の計算 Ba mod p とサーバ側の計算 Ab mod p が更新されます。a, b をどう選んでも 2 つの計算結果は同じ値になり、画面には「盗聴者に見える値(p, g, A, B)」と「見えない値(a, b, 共有秘密)」が色分けで表示されます。
なぜ盗聴者には分からないのか
盗聴者の手元には p = 23, g = 5, A = 8, B = 19 がそろっています。共有秘密を知るには、たとえば 5a mod 23 = 8 を満たす a を見つければよい — p = 23 なら総当たりですぐ見つかります。数が小さいから解けるのです。
実物の p は数百桁あります。べき乗して mod を取る「順方向」の計算は 2 乗の繰り返しで一瞬で終わるのに、結果から指数を逆算する「逆方向」(離散対数問題)は、知られているどの方法でも現実的な時間で終わりません。この順方向と逆方向の圧倒的な非対称性が、DH の安全性のすべてです。暗号化して隠しているのではなく、「逆算が間に合わない」ことで守っています。
実際の TLS 1.3 では、同じ考え方を楕円曲線という別の計算の場で行う ECDH が使われます。しかも秘密の値 a, b は接続のたびに新しく作って使い捨てます(ECDHE の E = ephemeral、一時的)。この使い捨てが効いてくるのが前方秘匿性です — 暗号文を全部記録していた攻撃者が、後年サーバの長期秘密鍵を手に入れても、使い捨ての値はとうに破棄されているので、過去のセッションの鍵は再現できません。
小問 3-2 — 盗聴者に何が見えて、何が見えないか
手計算した鍵合意を、今度は盗聴者の視点から眺め直します。
Q4. Diffie–Hellman 鍵合意の通信をすべて盗聴していた第三者に見えている値の組み合わせはどれですか。
経路を流れるのは p, g, A, B の 4 つだけです。秘密の値 a, b は各自の手元から一度も出ておらず、共有秘密 s も両者がそれぞれ手元で計算するだけで送信されません。「秘密を送らずに秘密を共有する」のが鍵合意の芯です。
Q5. 盗聴者は A = ga mod p を見ています。それでも共有秘密を計算できない理由として正しいものはどれですか。
g と p が分かっていても、g^a mod p = A を満たす a を求める問題(離散対数問題)は、p が実用サイズ(数百桁)になると現実的な時間で解けません。私たちが p = 23 で解けたのは数が小さいからです。A は平文で流れ、p も g も公開です — 守っているのは暗号化ではなく「逆算の難しさ」です。
Q6. TLS 1.3 の鍵合意は、接続のたびに使い捨ての値で行われます(ECDHE の E = ephemeral)。これで得られる「前方秘匿性」の説明として正しいものはどれですか。
通信ごとの鍵は使い捨ての値から作られ、使用後に破棄されます。攻撃者が暗号文を全部記録しておき、後年サーバの長期秘密鍵を手に入れても、その鍵から過去のセッションの鍵は導けません。「いま守れているか」だけでなく「将来鍵が漏れても過去が守れるか」まで考えるのが前方秘匿性です。
ただし、まだ大きな穴が残っている
手計算を終えたいま、1 つ意地悪な質問をします。あなたはいま、誰と鍵を合意したのでしょうか。
DH が保証するのは「公開値を交換した相手と秘密を共有できた」ことだけです。その相手が本物のサーバなのか、経路に割り込んだ攻撃者なのかは、DH 単体では分かりません。攻撃者が間に入り、あなたとも本物のサーバとも別々に鍵合意をすれば、すべての通信を復号・再暗号化しながら盗み読めます(中間者攻撃)。次章の主役 — 証明書 — は、まさにこの穴をふさぐ道具です。
この章で持ち帰ること
- DH 鍵合意は秘密を送らずに秘密を共有する。経路を流れるのは公開値だけ
- 安全性の根拠は離散対数の逆算が間に合わないこと。順方向(2 乗の繰り返し)との非対称性が鍵
- TLS 1.3 は使い捨ての値で鍵合意する(ECDHE)。これが前方秘匿性 — 将来の鍵漏えいから過去の通信を守る
- DH は「誰かと」鍵を作れるが「誰と」かは保証しない。その穴を埋めるのが次章の証明書