ディフィー・ヘルマン鍵交換とは|仕組みとTLS
ディフィー・ヘルマン鍵交換とは|仕組みとTLS
HTTPSのサイトを開く瞬間、画面の裏側では毎回の接続ごとに一時的な鍵が組み立てられています。そこで土台になっているディフィー・ヘルマンは暗号化そのものではなく、盗聴される回線の上でも、双方が同じ秘密を共同で作るための鍵合意プロトコルです。
HTTPSのサイトを開く瞬間、画面の裏側では毎回の接続ごとに一時的な鍵が組み立てられています。
そこで土台になっているディフィー・ヘルマンは暗号化そのものではなく、盗聴される回線の上でも、双方が同じ秘密を共同で作るための鍵合意プロトコルです。
この記事では、公開するのは p・g・A・B なのに、なぜ両者だけが同じ共有秘密 \(g^{ab}\) にたどり着けるのかを、小さな数値例で手計算できる形にほどいていきます。
紙とペンを手元に置いて、途中で本当に一致するか確かめながら読むと腹落ちします。
ここが暗号の美しいところなのですが、DH は単体では相手が本物かを保証しないため、中間者攻撃には弱いままです。
だから実運用ではTLSSSHIPsecのように認証と組み合わせて使われ、現代のTLS 1.3では一時鍵のE付き(EC)DHEによって、過去の通信を守る forward secrecy まで一本の流れで実現しています。
ディフィー・ヘルマン鍵交換は何を解決したのか
鍵配送問題とは
AESのような共通鍵暗号は、暗号化と復号に同じ鍵を使います。
そこが速さの源泉ですが、同時に最初の難所でもあります。
通信を始める前に、その「同じ鍵」を相手へ安全に渡さなければならないからです。
もしその受け渡しの途中を盗み見られたら、暗号文をどれだけ頑丈に作っても意味が薄れます。
これが鍵配送問題です。
公開鍵暗号が普及する前の発想では、鍵は人が会って手渡す、信頼できる専用回線で送る、あらかじめ紙や装置に埋め込んでおく、といった方法に頼りがちでした。
つまり「暗号通信を始める前に、すでに安全な別経路が必要」という逆説を抱えていたわけです。
ここで発想をひっくり返したのが、秘密そのものを送るのではなく、公開してよい値だけをやり取りして、両者が同じ秘密をその場で作るという考え方でした。
筆者はこの仕組みを説明するとき、色の調合の比喩をよく使います。
まず公開してよい共通の色を机の上に置き、送信側と受信側がそれぞれ自分だけの秘密の色を混ぜます。
混ぜた結果の色は相手に見せてもよいのですが、元の秘密の色そのものは明かしません。
そして互いに相手の混色へ自分の秘密色をもう一度混ぜると、両者の手元で同じ最終色ができます。
図にすると直感的で、DH の「秘密を送らずに共有秘密を作る」感覚がつかみやすくなります。
1976年の発明と意義
この転換点になったのが、1976年にWhitfield DiffieとMartin Hellmanが公表したディフィー・ヘルマン鍵交換です。
広い意味では公開鍵暗号の出発点にある技術ですが、厳密にはメッセージをそのまま暗号化する方式ではなく、鍵合意のプロトコルです。
ここを曖昧にすると、RSAのような暗号化方式と役割が混ざってしまいます。
DH が解決したのは、「共通鍵暗号を使いたいのに、最初の共通鍵を安全に配れない」という行き詰まりでした。
盗聴される通信路の上でも、事前共有秘密なしで共有秘密を作れる。
これが 1970 年代に登場した意義は大きく、以後の安全なネットワーク設計の土台になりました。
では、紙とペンで検算できる最小の例を見てみます。
実運用ではこんな小さな数は安全ではありませんが、仕組みの理解には最適です。
公開の値として素数 \(p=23\)、基数 \(g=5\) を使います。
これらは秘密ではなく、全員が知っていてよい値です。
送信側が秘密指数として \(a=6\) を選ぶと、公開値は
\[ A=g^a \bmod p = 5^6 \bmod 23 \]
です。順に計算すると、
\[ 5^2=25 \equiv 2 \pmod{23} \]
\[ 5^4 \equiv 2^2=4 \pmod{23} \]
\[ 5^6 \equiv 5^4 \cdot 5^2 \equiv 4 \cdot 2 = 8 \pmod{23} \]
なので、送信側は \(A=8\) を公開します。
受信側は秘密指数として \(b=15\) を選びます。公開値は
\[ B=g^b \bmod p = 5^{15} \bmod 23 \]
です。計算を分けると、
\[ 5^1 \equiv 5,\quad 5^2 \equiv 2,\quad 5^4 \equiv 4,\quad 5^8 \equiv 16 \pmod{23} \]
したがって
\[ 5^{15}=5^{8+4+2+1} \equiv 16 \cdot 4 \cdot 2 \cdot 5 \pmod{23} \]
\[ 16 \cdot 4=64 \equiv 18,\quad 18 \cdot 2=36 \equiv 13,\quad 13 \cdot 5=65 \equiv 19 \pmod{23} \]
となり、受信側は \(B=19\) を公開します。
ここからが DH の核心です。送信側は相手の公開値 \(B=19\) を受け取り、
\[ s=B^a \bmod p = 19^6 \bmod 23 \]
を計算します。
\[ 19^2=361 \equiv 16,\quad 19^4 \equiv 16^2=256 \equiv 3 \pmod{23} \]
19^6 \equiv 19^4 \cdot 19^2 \equiv 3 \cdot 16 = 48 \equiv 2 \pmod{23} \]
なので、送信側の得る共有秘密は \(s=2\) です。
受信側も同じように相手の公開値 \(A=8\) を使って
\[ s=A^b \bmod p = 8^{15} \bmod 23 \]
を計算します。
\[ 8^2=64 \equiv 18,\quad 8^4 \equiv 18^2=324 \equiv 2,\quad 8^8 \equiv 2^2=4 \pmod{23} \]
したがって
\[ 8^{15}=8^{8+4+2+1} \equiv 4 \cdot 2 \cdot 18 \cdot 8 \pmod{23} \]
\[ 4 \cdot 2=8,\quad 8 \cdot 18=144 \equiv 6,\quad 6 \cdot 8=48 \equiv 2 \pmod{23} \]
となり、受信側も同じく \(s=2\) に到達します。
両者で値が一致したので検算成功です。数式の形で見ると、
\[ B^a \equiv (g^b)^a \equiv g^{ba} \equiv g^{ab} \equiv (g^a)^b \equiv A^b \pmod p \]
となるため、計算順序が違っても同じ共有秘密に着地します。
ここが暗号の美しいところなのですが、公開しているのは \(p\)、\(g\)、\(A\)、\(B\) だけで、秘密指数 \(a\) と \(b\) は送っていません。
それでも双方だけが同じ値を作れます。
ℹ️ Note
いま使った \(p=23\) や \(g=5\) のような小さな数は、仕組みを手で確かめるための教材です。実運用では安全性が足りず、有限体DHなら少なくとも 2048 ビット級、現代のTLSではECDHEも広く使われます。共有秘密もそのまま暗号鍵にせず、ハッシュやHKDFのような KDF を通して利用します。
他方式との位置づけ
DH を理解するときは、「何をしてくれる方式なのか」を役割で切り分けると混乱しません。
共通鍵暗号はデータを速く暗号化する担当です。
DH はその前段で、通信相手と共有する材料を作る担当です。
つまり、競合関係ではなく分業関係にあります。
RSAとの違いもここにあります。
歴史的には RSA を鍵配送に使う構成も広く使われましたが、RSA は公開鍵で秘密を包んで送る発想です。
一方の DH は、秘密を直接送らず、双方の計算結果として共有秘密を得る発想です。
現代のTLS 1.3では古い static RSA 型の鍵交換は整理され、接続ごとに一時鍵を作る(EC)DHEが中心になっています。
HTTPS を開くたびに毎回別の共有秘密が組み上がるのは、この流れに沿っています。
この話が示唆的なのは、DH が「公開値だけを見せても秘密は出ない」という数学的性質を持っていても、どのグループを選び、どの強度で運用するかが安全性を左右するという現実です。
とくに有限体 DH では、広く使い回される共通 prime が攻撃者にとって都合のよい標的になります。
2015年に報告された Logjam の評価では、トップ100万ドメインの約 8.4% が脆弱であると観測されました。
2015年時点の初期観測では、トップ100万ドメインの約8.4%が脆弱であると報告されました。
一般的な目安として、ECC(P-256)は約128ビットの安全性を提供すると見なされ、実務上は RSA 3072 ビット相当の強度の目安とされることが多いですが、実装や脅威モデルに依存する概算です。
仕組みの核心:秘密を送らずに同じ値を作る
公開パラメータpとg
ディフィー・ヘルマンの出発点は、参加者どうしで 公開パラメータ をそろえることです。
ここで使うのが大きな素数 \(p\) と、底になる \(g\) です。
どちらも秘密ではなく、通信を見ている第三者に知られても構いません。
秘密にするのは、そのあと各自が選ぶ指数の側です。
直感的には、\(p\) は「時計の文字盤の大きさ」、\(g\) は「何回掛け算を重ねるかの出発点」と考えるとつかみやすくなります。
モジュラ計算、つまり \(p\) で割った余りだけを見る世界では、数を何度も掛けていくと値が文字盤の上をぐるぐる回ります。
この回り方を決める舞台装置が \(p\) と \(g\) です。
筆者はこの段階を説明するとき、絵でいうなら「同じミキサーと同じ容器を全員に配る場面」だと考えています。
ミキサーの型番が公開されていても困らないのと同じで、DHでも \(p\) と \(g\) が公開であること自体は問題になりません。
秘密の味を決めるのは、各自があとから入れる材料、つまり \(a\) や \(b\) のほうです。
A=g^a mod p と B=g^b mod p
次に、各参加者は自分だけの秘密値を選びます。
送信側が \(a\)、受信側が \(b\) を持つとしましょう。
この \(a\) と \(b\) は外に出しません。
その代わりに、公開してよい値として
\[ A=g^a \bmod p,\qquad B=g^b \bmod p \]
を計算して交換します。
ここで感覚をつかむには、小さな数で見るのがいちばんです。
前の手計算と同じく \(p=23\)、\(g=5\) を使うと、たとえば \(a=6\)、\(b=15\) から
\[ A=5^6 \bmod 23 = 8,\qquad B=5^{15} \bmod 23 = 19 \]
が得られます。通信路に流れるのは \(23\)、\(5\)、\(8\)、\(19\) です。肝心の \(6\) と \(15\) は流れていません。
ここが直感に反するかもしれませんが、DHでは「秘密を送る」のではなく「秘密から作った公開値を送る」わけです。
しかもその変換は、前向きには軽い計算で済みます。
\(g\) を何回か掛けて、途中で \(\bmod p\) を取ればよいからです。
筆者はこれを、絵の具を混ぜる作業に近いと感じます。
青と黄を混ぜて緑を作るのは一瞬ですが、目の前の緑だけを見て「青を何滴、黄を何滴入れたか」をぴたりと当てるのは急に難しくなります。
DHの公開値 \(A\) や \(B\) も、それとよく似た振る舞いを見せます。
共有秘密S=g^(ab) mod pの一致
交換が終わったら、各自は相手から受け取った公開値に、自分の秘密指数をもう一度かけます。送信側は
\[ S=B^a \bmod p \]
を計算し、受信側は
\[ S=A^b \bmod p \]
を計算します。
式を展開すると、
\[ B^a \equiv (g^b)^a \equiv g^{ba} \equiv g^{ab} \pmod p \]
\[ A^b \equiv (g^a)^b \equiv g^{ab} \pmod p \]
となります。掛け算の順番が違っても \(ab=ba\) なので、両者は同じ値に到達します。これが共有秘密
\[ S=g^{ab} \bmod p \]
です。
前の数値例をそのまま使えば、送信側は \(19^6 \bmod 23\)、受信側は \(8^{15} \bmod 23\) を計算し、どちらも \(S=2\) に着地しました。
見えている材料は同じでも、第三者は \(a\) も \(b\) も持っていないので、この「もう一段の指数計算」を同じ形で再現できません。
この一致は、暗号の中でもとくに見事な点です。
普通は、同じ秘密を共有したければ、その秘密自体を安全に渡す仕組みが必要になります。
ところがDHでは、秘密そのものを送っていないのに、両端だけが同じ値にたどり着きます。
公開値を一度交換したあと、各自の手元にある非公開の指数が最後のピースとしてはまり、同じ \(g^{ab}\) が完成するわけです。
離散対数問題と一方向性
では、なぜ盗み見している第三者は困るのでしょうか。
理由は、DHの計算に 前向きと逆向きの非対称性 があるからです。
\(g\)、\(p\)、\(a\) が分かっていれば \(A=g^a \bmod p\) を計算するのは難しくありません。
ところが \(g\)、\(p\)、\(A\) だけを見て、「では \(a\) は何か」を逆算するのは現実的に難しい。
この逆算の困難さが、有限体版DHでは離散対数問題に結びつきます。
筆者はこの性質を、数字の層が何枚も重なった圧縮ファイルのように捉えることがあります。
掛け算やべき乗で前に進むときは、順番に混ぜていくだけです。
しかし逆向きに戻ろうとすると、どこで何回混ぜたかという履歴が失われています。
しかも \(\bmod p\) を取るたびに、途中経過は余りだけに折りたたまれます。
作るのは簡単、元の指数を言い当てるのは難しい。
この「混ぜるのは易しいが、分解は難しい」という感覚が、一方向性の核心です。
もちろん、教材用の小さな数では総当たりで逆算できます。
\(p=23\) の世界なら、\(5^1, 5^2, 5^3, \dots\) を順に調べれば、\(A=8\) に対応する指数を見つけられます。
安全性が立ち上がるのは、実運用で使う大きなパラメータの領域です。
そこでは公開値から秘密指数を取り戻す計算量が跳ね上がり、現実の攻撃コストに見合わなくなります。
ここで一つ切り分けておくと、DHが守っているのは「秘密指数を逆算されにくいこと」です。
相手が本物かどうかの確認は別の仕組みが担当します。
前述の通り、DH単体は認証を持たないので、公開値を見て同じ共有秘密を作れるという性質と、通信相手の正当性は別問題です。
共有秘密からのKDFによる鍵導出
実務では、得られた共有秘密 \(S\) をそのまま暗号鍵として使いません。
ここも仕組みの理解で見落とされやすい点です。
DHが作る \(S\) はあくまで「両者だけが共有できた材料」であって、そのまま最終鍵に直結させるより、ハッシュやKDFを通して整形したほうが筋が良いからです。
理由は二つあります。
ひとつは、共有秘密のビット列がそのまま理想的な鍵の形をしているとは限らないことです。
鍵として使いたい長さにぴたり一致するとは限りませんし、表現のされ方によっては先頭側に偏りが出る場面もあります。
もうひとつは、鍵分離 のためです。
通信の暗号化用、完全性確認用、クライアント側とサーバ側の送信方向用といった具合に、用途ごとに別の鍵へ分けておくと、ひとつの用途の問題が別用途へ波及しません。
この感覚は、ひとつの生地玉からパン、ピザ、麺を全部そのまま切り出すのではなく、用途ごとにこね直して形を整える場面に近いです。
元の材料は同じでも、焼くものと伸ばすものでは欲しい性質が違います。
共有秘密 \(S\) も同じで、そのまま握って使うより、KDFで「必要な長さ」「必要な用途」に合わせて整えたほうが、鍵としての扱いが明快になります。
代表例がHKDFです。
HKDFは Extract と Expand の二段構えで、まず入力材料から扱いやすい擬似乱数鍵を取り出し、次に用途情報を混ぜながら必要な長さの鍵列へ広げます。
たとえばTLS 1.3の鍵スケジュールでも、この考え方でハンドシェイク鍵やアプリケーション鍵が分けて導出されます。
筆者が実装検証で図に描くときは、中央に共有秘密 \(S\) を置き、そのまま一本の鍵として矢印を伸ばすのではなく、いったんKDFの箱に通してから「送信用」「受信用」「完全性確認用」のように枝分かれさせます。
こう描くと、Sをそのまま鍵にしない理由 が視覚的にはっきりします。
生の材料を直接使うのではなく、偏りをならし、役割ごとに別鍵へ切り出すためです。
💡 Tip
HKDFは入力材料を抽出してから展開する構造を持ち、必要な長さの鍵素材を作れます。
たとえばSHA-256を使う場合、展開で得られる出力は 8,160 バイトまで伸ばせます(詳しくは RFC 5869 を参照:
小さな数字で追うディフィー・ヘルマンの手順
(HKDF の仕様: RFC 5869 を参照:
パラメータのセットアップ
ここでは、紙とペンで追える大きさまで数字を縮めて、ディフィー・ヘルマンの流れを手でなぞります。
使う公開パラメータは素数 \(p=23\) と基数 \(g=5\) です。
どちらも全員に見えていて構いません。
秘密にするのは、送信側と受信側がそれぞれ選ぶ指数だけです。
具体例として、片方の秘密指数を \(a=6\)、もう片方を \(b=15\) にします。
すると両者はそれぞれ、自分の指数だけを隠したまま公開値を作ります。
ここが暗号の美しいところなのですが、同じ \(p\) と \(g\) を共有しながら、秘密指数だけが別々なので、途中で見えている数字が同じでも手元の計算経路は異なります。
この節では小さな数を使いますが、これはあくまで教材用です。
手で追えるということは、攻撃側も総当たりで追えてしまうということでもあります。
実務での有限体DHは前述の通りもっと大きなパラメータを使い、現代のTLS 1.3ではECDHEが主流です。
公開値の計算
まず \(A=g^a \bmod p\) を計算します。今回なら
\[ A=5^6 \bmod 23 \]
です。いきなり \(5^6=15625\) を作ってから 23 で割るより、途中で余りを取り続けたほうが計算の筋が見えます。
\[ 5^2=25 \equiv 2 \pmod{23} \]
\[ 5^4=(5^2)^2 \equiv 2^2=4 \pmod{23} \]
\[ 5^6 = 5^4 \cdot 5^2 \equiv 4 \cdot 2 = 8 \pmod{23}
したがって公開値は \(A=8\) です。
同様に、もう一方は
\[ B=5^{15} \bmod 23 \]
を計算します。
ここで筆者が手計算で楽しいと感じるのは、指数を分割して組み立てる瞬間です。
たとえば \(15\) は \(8+4+2+1\) に分けられるので、二乗反復で余りを更新していくと、べき乗が「巨大な数」ではなく「必要な部品の積」に見えてきます。
\[ 5^1 \equiv 5 \pmod{23} \]
5^2 は 25 で、23 で割ると余りは 2 です。
式で書くと 5^2 \equiv 2 \pmod{23} となります。
5^2 \equiv 2 \pmod{23} \]
\[ 5^4 \equiv 4 \pmod{23} \]
\[ 5^8 \equiv 16 \pmod{23} \]
なので、
\[ 5^{15}=5^{8+4+2+1} \equiv 16 \cdot 4 \cdot 2 \cdot 5 \pmod{23} \]
と分解できます。順に余りを取ると、
\[ 16 \cdot 4=64 \equiv 18 \pmod{23} \]
\[ 18 \cdot 2=36 \equiv 13 \pmod{23} \]
\[ 13 \cdot 5=65 \equiv 19 \pmod{23} \]
となるので、公開値は \(B=19\) です。
この時点で第三者に見えているのは \(p=23\)、\(g=5\)、\(A=8\)、\(B=19\) だけです。
秘密指数の \(a=6\)、\(b=15\) は送っていません。
共有秘密の一致を検算
次に、互いの公開値に自分の秘密指数を掛けるように見える計算をします。送信側は
\[ S=B^a \bmod p = 19^6 \bmod 23 \]
を計算します。これも二乗反復で追えます。
19^6 = 19^4 \cdot 19^2 \equiv 3 \cdot 16 = 48 \equiv 2 \pmod{23} 19^2=361 \equiv 16 \pmod{23} \]
\[ 19^4 \equiv 16^2=256 \equiv 3 \pmod{23} \]
\[ 19^6=19^4 \cdot 19^2 \equiv 3 \cdot 16=48 \equiv 2 \pmod{23} \]
したがって共有秘密は \(S=2\) です。
もう一方は
\[ S=A^b \bmod p = 8^{15} \bmod 23 \]
を計算します。
\[ 8^2=64 \equiv 18 \pmod{23} \]
\[ 8^4 \equiv 18^2=324 \equiv 2 \pmod{23} \]
\[ 8^8 \equiv 2^2=4 \pmod{23} \] 8^8 は 256 で、23 で割ると余りは 4 です。
式で書くと 8^8 \equiv 4 \pmod{23} となります。
ここでも \(15=8+4+2+1\) と分けると、
\[ 8^{15}=8^8 \cdot 8^4 \cdot 8^2 \cdot 8 \equiv 4 \cdot 2 \cdot 18 \cdot 8 \pmod{23} \]
です。順に計算すると、
\[ 4 \cdot 2=8 \] まず 4 と 2 を掛けると 8 になり、次に 8 と 18 を掛けると 144 で 23 で割ると余りは 6 です。
最後に 6 と 8 を掛けると 48 で余りは 2 です。
\[ 8 \cdot 18=144 \equiv 6 \pmod{23} \]
\[ 6 \cdot 8=48 \equiv 2 \pmod{23} \]
となり、こちらも \(S=2\) になりました。
両者で同じ値に着地する理由は式にすると一行です。
B^a = (g^b)^a = g^{ba} = g^{ab} = (g^a)^b = A^b \pmod{p}。
この等式は、掛け算の順序が入れ替わっても結果が同じことを表しています。
\[ B^a=(g^b)^a=g^{ba}=g^{ab}=(g^a)^b=A^b \pmod{p} \]
ですが、実際に小さな数字で検算すると、その一行がただの記号操作ではなく「本当に同じ余りになる」という感触で腹落ちします。
筆者はこの段階で、途中の巨大な値を追うのではなく、余りだけを持ち回ることに暗号らしい手触りを感じます。
元の数は膨れ上がっても、手元に残すのは毎回その余りだけです。
その圧縮があるからこそ、計算は進むのに逆向きは急に見通しが悪くなります。
練習課題と安全上の注意
ここまで追えたら、自分で指数だけ差し替えて同じ一致を作ってみると理解が一段深まります。
\(p=23\)、\(g=5\) はそのままにして、好きな \(a\) と \(b\) を選び、まず \(A=5^a \bmod 23\)、\(B=5^b \bmod 23\) を出し、その後で \(B^a \bmod 23\) と \(A^b \bmod 23\) を比べてみてください。
小さな課題ですが、公開値を先に作ってから共有秘密の一致を確かめる流れを自分の手で一巡すると、DHの骨格が記憶に残ります。
おすすめの進め方は、指数を二進数の感覚で分けることです。
たとえば \(11\) なら \(8+2+1\)、\(13\) なら \(8+4+1\) と見て、\(g^1\)、\(g^2\)、\(g^4\)、\(g^8\) を順番に二乗で作り、必要な部品だけ掛け合わせます。
このやり方に慣れると、べき乗計算は「巨大な数を一気に作る作業」ではなく、「余りを更新しながら部品を拾う作業」に変わります。
ℹ️ Note
この節の小さな数は安全ではありません。 実運用では手計算できる規模のDHは使いません。有限体DHを使うなら少なくとも 2048 ビット以上のグループが前提になり、現代の通信ではエフェメラルなECDHEが中心です。
この注意を踏まえたうえで小さな例に触れる価値は、数学の流れを自分の指先で確かめられることにあります。
公開してよい値と隠すべき値がどこで分かれ、どの時点で同じ共有秘密に合流するのかは、実際に一度計算すると抽象図よりも鮮明に見えてきます。
なぜ盗聴には強く、中間者攻撃には弱いのか
盗聴モデルと離散対数
ディフィー・ヘルマンが強いのは、まず「回線を眺めているだけの盗聴者」に対してです。
攻撃者が見られるのは、前節の小さな例で言えば \(p\)、\(g\)、公開値 \(A=g^a \bmod p\)、\(B=g^b \bmod p\) までです。
ここから共有秘密 \(g^{ab}\) に届くには、どこかで秘密指数 \(a\) か \(b\) を突き止める必要があります。
その逆算は、有限体上では離散対数問題にぶつかります。
つまり「\(g^a \equiv A \pmod p\) となる \(a\) を求めよ」という問題です。
ここが暗号の美しいところなのですが、順方向の計算、つまり \(g^a \bmod p\) を作る処理は現実の計算機で進められる一方で、逆方向に \(a\) を取り戻す計算は急に重くなります。
十分大きなグループを選んだ前提では、盗聴者が \(p\)、\(g\)、\(A\)、\(B\) を全部見ていても、その場で秘密指数を復元することは現実的ではありません。
だから、秘密そのものを回線に流していないのに、両者だけが同じ共有値を得られます。
直感に反するかもしれませんが、DHの安全性は「公開値を隠しているから」ではなく、「公開値から秘密指数へ戻る計算が難しいから」に支えられています。
前節で小さな数を手で追ったときは、総当たりでも秘密指数を見つけられそうに見えたはずです。
あれは理解のために世界を縮めていたからです。
実運用では、そうした見通しの良さが消える大きさまでパラメータを引き上げます。
ただし、この強さはあくまで盗聴モデルに対するものです。
言い換えると、DH単体が保証しているのは「誰かが横から見ていても共有秘密を取りにくい」という性質であって、「今通信している相手が本当に想定した相手か」という正当性までは証明しません。
ここに次の弱点が出てきます。
MITM攻撃の典型シナリオ
DH単体は非認証の鍵合意です。つまり、鍵を作ることはできても、相手の身元を確認していません。この穴を突くのが MITM(中間者攻撃)です。
典型例では、送信者Aと受信者Bの間に攻撃者Mが割り込みます。
Aは本来Bに送るはずの公開値 \(A=g^a\) を回線に流しますが、Mはそれを受け取ってBには渡さず、自分の公開値 \(M_1=g^m\) をBに送ります。
反対側でも同じことを行い、Bが送った公開値 \(B=g^b\) をAには渡さず、別の自分の公開値 \(M_2=g^{m'}\) をAに返します。
するとAは「Bと鍵を共有した」と思って \(S_{AM}\) を計算し、Bも「Aと鍵を共有した」と思って \(S_{MB}\) を計算します。
実際には、AとBのあいだに1本の共有秘密ができたのではなく、A-M 間と M-B 間に別々の共有秘密が2本できています。
筆者はこの説明をするとき、矢印図を頭の中で文章になぞる小さな演習をよくします。
Aが暗号化して送ったメッセージがMに届いた瞬間、「今、このメッセージは誰の鍵で見えているか」を追うのです。
Aが使ったのはA-Bの鍵ではなくA-Mの鍵なので、Mはそこで復号できます。
Mは内容をそのままでも書き換えてでもよく、今度はM-Bの鍵で再暗号してBへ送ります。
Bから見ると、自分の鍵で正しく読めるので違和感がありません。
逆方向も同じです。
AはBと話しているつもり、BはAと話しているつもりですが、平文を見ているのは途中のMです。
この攻撃が成立する理由は、DHの数式が破られたからではありません。
\(g^{ab}\) の作り方そのものは正しいままです。
問題は、その \(g^a\) や \(g^b\) が「本当に相手本人が出した公開値か」を確認していない点にあります。
暗号プロトコルの設計では、この区別がとても欠かせません。
盗聴耐性と相手認証は別の性質で、前者を満たしても後者は自動では付いてきません。
この感覚は、SSHの初回接続でホスト鍵のフィンガープリントを見たときに腹落ちしやすいものです。
あの確認画面が出るのは、まさに「今つながっているこの公開鍵は、本当にそのサーバのものか」を人間に問う場面だからです。
そこを飛ばして受け入れると、通信路の暗号化自体は動いていても、相手の正体を取り違えたまま安全だと信じ込む余地が残ります。
認証と組み合わせて初めて安全域が広がる
DHを実用で安全に使うには、認証を必ず組み合わせます。
鍵合意で共有秘密を作り、別の仕組みで「その公開値を出した主体が誰か」を縛るわけです。
ここで登場するのが、署名、証明書、PSK(事前共有鍵)です。
WebのTLSでは、サーバは証明書を提示し、その証明書に結び付いた秘密鍵でハンドシェイクの内容へ署名します。
これによって、受け取った鍵共有パラメータが途中で差し替えられていないことと、そのサーバ名に対応する正当な主体が応答していることを同時に確認できます。
現代のTLS 1.3は認証と鍵交換を分離しており、証明書鍵は署名に使い、鍵交換には一時的な(EC)DHEの鍵を使います。
この組み合わせによって、相手認証と forward secrecy を両立させています。
SSHではホスト鍵がその役割を担います。
初回接続でフィンガープリントを確認し、以後はその鍵が変わっていないかを追跡する流れです。
利用者の体験としては素朴に見えますが、やっていることは本質的です。
鍵交換だけでは埋まらない「その公開鍵は誰のものか」という穴を、ホスト鍵の継続的な確認で塞いでいます。
IPsecのIKEv2でも考え方は同じです。
初期交換でDH値をやり取りして共有秘密を作り、その後の認証段階で証明書やPSKを使って相手を確かめます。
つまり、DHは単独で完成品なのではなく、認証付き鍵合意の部品として働くときに本来の力を発揮します。
PSKも有効な選択肢です。
あらかじめ共有した鍵を知っている者だけが正当な参加者だと示せるからです。
ただし、現代の設計ではPSKだけで閉じるより、PSKとエフェメラルな(EC)DHEを併用する構成のほうが守備範囲が広がります。
認証の足場をPSKで確保しつつ、その接続ごとに新しい共有秘密を作れるからです。
要するに、DHが強いのは「秘密を送らずに同じ値を作る」部分であり、相手の正体を名乗らせる機能は別途必要です。
離散対数問題が盗聴を止め、署名や証明書やPSKがなりすましを止める。
実用プロトコルはこの二層構造でできています。
DHE・ECDHE・FFDHEの違いとTLS 1.3
FFDHEとECDHEの違い
現代の実装でまず押さえたいのは、DHE という名前は「一時鍵を使うDH」全般を指す文脈で使われる一方、その土台となる数学は2系統あるという点です。
有限体上の古典的なDHを使うものがFFDHE、楕円曲線上のDHを使うものがECDHEです。
どちらも役割は同じで、通信のたびに共有秘密を合意することにありますが、計算の舞台が違います。
FFDHEは有限体上の離散対数問題を土台にしています。
古典的な \(g^a \bmod p\) の世界そのもので、DHの教科書的な説明はたいていこちらです。
対してECDHEは楕円曲線離散対数問題を使います。
見かけの数式は別物でも、「公開値を交換して、双方だけが同じ共有秘密にたどり着く」という構図は共通です。
ここが暗号の美しいところなのですが、同じ“鍵共有”でも、数学的な足場を変えることで効率と鍵長のバランスが変わります。
実務上は、同等の安全性を狙うなら ECDHE のほうが短い鍵長で済むため、通信量と計算量の両面で有利になりやすいのが利点です。
代表的な目安として、ECC(P-256)は約128ビットの安全性を与えるとされ、実務上は RSA 3072 ビット相当の強度の目安とされることが多い(実装・脅威モデルに依存する概算)。
有限体DHでも十分な強度は作れますが、そのぶんパラメータは長くなります。
現代のHTTPSで多く見かけるのがECDHEなのは、この効率の差がそのまま運用上の利点になるからです。
たとえばX25519やsecp256r1のような曲線は、TLS の鍵共有で広く使われています。
筆者が検証でトラフィックを見ていても、証明書の公開鍵アルゴリズムはRSAやECDSAなのに、実際の鍵共有そのものはECDHEで進んでいる構成に頻繁に出会います。
最初は「証明書がRSAなのだから鍵交換もRSA系なのでは」と感じる読者も多いのですが、現代TLSではその直感は当たりません。
証明書の鍵と、接続ごとの鍵共有は、役割がきっぱり分かれています。
代表的な目安として、ECC(P-256)は約128ビットの安全性を与えるとされ、しばしば RSA 3072 ビット相当の目安として扱われますが、これはあくまで概算であり、実装や脅威モデルに依存します。
Ephemeralとforward secrecy
DHEやECDHEの E は Ephemeral、つまり「その接続のあいだだけ使う一時鍵」を意味します。
ここが static DH との分かれ目です。
長期に使い回す固定鍵ではなく、セッションごとに新しい秘密値を生成し、その場限りの公開値を交換するので、あとから長期鍵が漏れたとしても、過去の通信内容までまとめて読まれにくくなります。
これが forward secrecy、あるいは前方秘匿性です。
直感に反するかもしれませんが、TLSで本当に守りたいのは「今この接続」だけではありません。
数か月後、あるいは数年後にサーバの長期秘密鍵が漏えいしたとしても、過去に記録されていた通信まで一斉に解読されないことに意味があります。
Ephemeral な鍵交換は、そのための仕掛けです。
証明書の秘密鍵はサーバ本人の証明に使われますが、通信を暗号化するための素材は、毎回あらためて作る。
長期鍵とセッション鍵を切り分けることで、被害の波及をそこで止めます。
この違いは、ブラウザの開発者ツールを開くと急に具体物になります。
ChromeやFirefoxで任意のHTTPSサイトを開き、セキュリティ情報や接続詳細を見ると、TLS のバージョンとともに鍵交換の種類が読める場面があります。
そこにTLS 1.3やECDHE系の情報が並んでいる一方で、証明書欄にはRSA公開鍵やECDSA署名が見えることがあります。
筆者はこの表示を初学者に見てもらうとき、「いま表示されている“証明書の鍵の種類”と、“接続ごとに動いた鍵共有”は別レイヤーの話です」と説明します。
画面上で並んでいるので混同しやすいのですが、そこを切り分けると TLS の設計が一気に立体的に見えてきます。
forward secrecy は、DH を使っただけでは自動で手に入る性質ではありません。
一時鍵を使う DHE/ECDHE であることが条件です。
FFDHE でも ECDHE でも、Ephemeral に運用していれば前方秘匿性を持てます。
逆に、長期固定の鍵共有に寄せた設計では、この利点は失われます。
現代プロトコルが「その場で作って、その場で捨てる」方向へそろっているのは、単に流行だからではなく、過去通信の保護まで視野に入れた結果です。
ℹ️ Note
証明書の秘密鍵が漏れたら直ちに全通信が読める、というイメージは TLS 1.3 では当たりません。証明書鍵は認証のための長期鍵であり、ハンドシェイクで実際に共有秘密を作るのは接続ごとの一時的な(EC)DHE鍵です。
TLS 1.3のkey_shareと認証鍵の分離
TLS 1.3(RFC 8446)では、この分離が仕様としていっそう明快になりました。
古い時代の static RSA や static DH に依存する鍵交換は姿を消し、ephemeral な(EC)DHEを中心にハンドシェイクを組み立てる形になっています。
クライアントはClientHelloで key_share 拡張を送り、そこに自分が使いたいグループと公開共有値を載せます。
ここでの key_share は、証明書とは別の世界のデータです。
証明書に入っている公開鍵は長期の認証鍵、key_share に入る公開値はその接続限りのDH一時公開値です。
TLS 1.3 の理解でつまずきやすいのは、この二つの公開鍵が同じ「公開鍵」という言葉で呼ばれることです。
役割まで同じだと思うと混乱します。
証明書鍵は「私はこのドメインの正当な主体です」と署名で示すためにあり、key_share は「この接続で使う共有秘密を一緒に作りましょう」と合意するためにあります。
認証と鍵交換が明確に分離された、と捉えると整理できます。
ここでの key_share は、証明書とは別の世界のデータです。
証明書に入っている公開鍵は長期の認証鍵、key_share に入る公開値はその接続限りのDH一時公開値です。
実機の観察でもこの構図はよく見えます。
たとえば、あるサイトのサーバ証明書がRSA公開鍵だったとしても、ハンドシェイクの鍵共有がECDHEで進むことは普通にあります。
反対に、証明書がECDSAでも、鍵共有ではX25519のような別の楕円曲線グループが選ばれます。
筆者はパケット解析やブラウザ表示を追うとき、ここを意識的に分けて見ます。
証明書チェーンの欄は認証の話、key_share や negotiated group の欄は鍵合意の話、という具合です。
これが見えてくると、「RSA証明書のサイトなのに ECDHE と表示されるのはなぜか」という疑問が自然に解けます。
TLS 1.3 では、この key_share から得た shared secret をそのまま通信鍵にするのではなく、HKDFを使う key schedule に流し込み、ハンドシェイク用・アプリケーション用の鍵素材へ段階的に展開します。key_share は鍵導出の出発点であって、認証鍵そのものでも完成済みの通信鍵でもありません。
DH の一時鍵、証明書の長期鍵、そこから派生するレコード保護用の対称鍵がきれいに分業しているのが TLS 1.3 の姿です。
実装上の位置づけとして見ると、現代TLSは「証明書の鍵アルゴリズムは RSA でも ECDSA でもよいが、鍵共有はエフェメラルな (EC)DHE で行う」という構成が基本線です。
とくにECDHEは効率の面で扱いやすく、現場では最も目にする選択肢になっています。
一方でFFDHEも消えたわけではなく、有限体ベースの標準グループを使う選択肢として残っています。
TLS 1.3 の本質は「楕円曲線しか使わない」ことではなく、静的な鍵交換をやめ、接続ごとの一時鍵共有を標準に据えたことにあります。
Logjam 以後の実務上の教訓
Logjamの概要
Logjamが突きつけたのは、ディフィー・ヘルマンの理屈そのものより、どのグループを選び、どの強度で運用するかが安全性を左右するという現実でした。
攻撃の焦点になったのは、1990年代の輸出規制の名残で残っていたDHE_EXPORTです。
ここでは 512 ビット級の有限体 DH が使われ、通信経路上でダウングレードを誘発できる構成と、弱い DH パラメータの再利用が重なると、現代の計算能力では無視できない危険になります。
この話が示唆的なのは、DH が「公開値だけを見せても秘密は出ない」という数学的性質を持っていても、現場で選ばれる prime や group が弱ければ全体が崩れることです。
とくに有限体 DH では、広く使い回される共通 prime が攻撃者にとって都合のよい標的になります。
個々の接続をその都度ゼロから解くのではなく、同じ弱い group を使うサーバ群に対して前計算の価値が生まれるからです。
直感に反するかもしれませんが、共有パラメータは「みんなで使うと便利」な反面、「みんなで同じ弱点を抱える」状態も作ります。
2015年時点の観測では、トップ 100 万ドメインの 8.4% がDHE_EXPORTに脆弱な状態にありました。
ここで問題になったのは単なる古さではなく、古い互換性設定が、いまの攻撃面として残っていたことです。
筆者はサーバ設定を点検するとき、暗号スイートの一覧に目が行きがちでも、実際には「どの DH パラメータを許しているか」のほうが事故の芽を抱えやすいと感じます。
設定ファイルの一行は短くても、その背後には輸出規制時代の遺産、共有 prime の再利用、前方秘匿性の設計思想まで折り重なっています。
この話が示すのは、楕円曲線ベースの鍵共有は同等強度なら有限体 DH より短い鍵長で済み、効率面で有利になりやすいという点です。
業界ではしばしば "ECC 256bit ≒ RSA 3072bit" といった目安が用いられますが、これは概算であり、実装や脅威モデルによって評価が変わる点に注意が必要です。
実務対策チェックリスト
業界でしばしば用いられる換算(例: "ECC 256bit ≒ RSA 3072bit")は分かりやすい目安ですが、実際の強度比較はアルゴリズムや実装、脅威モデルに左右されるため、概算として扱うのが適切です。
実務で見るべき点は、抽象論より設定の具体です。
筆者が実サーバの sample 設定を読むときは、まず「古いものを消せているか」と「残すなら何を残すか」の二段で見ます。
Apache でもNginxでもHAProxyでも発想は同じで、TLS の設定断片に DHE や DH という文字列が出てきたら、その場で有限体グループの扱いを確認します。
ここを流すと、証明書や TLS バージョンが新しく見えても、足元のグループ運用だけ古いまま残ることがあります。
確認項目は次の通りです。
- DHE_EXPORTを無効化しているか
- 有限体 DH を使う場合、2048 ビット以上のパラメータを使っているか
- 独自生成の曖昧な group ではなく、安全な共有 prime か標準 group を使っているか
- 可能なら鍵共有の主軸をECDHEへ移しているか
- サーバ設定の更新時に、暗号スイートだけでなく DH パラメータ定義も見直しているか
この順番で見ると、設定の読み解き方がぶれません。
たとえば ssl_ciphers だけ眺めて安心するのは危険で、古い構成では別ファイルの dhparam 指定や、過去に生成した DH パラメータが残っていることがあります。
自宅サーバや小規模な公開環境ほど、初回セットアップ時の設定が長く生き残りやすく、そこに古い 1024 ビット以下の有限体 DH や、由来の曖昧なパラメータが居座ります。
筆者は設定レビューでこの箇所を見るたび、「TLS はバージョン表記だけでは健康診断にならない」と実感します。
ℹ️ Note
有限体 DH を残すなら、2048 ビット以上を最低線として扱い、運用の主軸はECDHEに置く構成が今の基準です。
運用面では、グループ固定の考え方も改めたいところです。
昔は「一度作った DH パラメータを長く使う」運用が珍しくありませんでしたが、Logjam以後は、そもそも弱い共通 prime を避け、標準化済みの安全な group を採る方向へ整理されています。
有限体 DH を使う事情があるなら、どの prime を使っているか、どの設定ファイルで指定されているか、更新時に再生成や差し替えが必要かを、設定管理の単位として持っておくべきです。
暗号の美しいところは数学にありますが、事故を防ぐのは設定の棚卸しです。
RFC 7919のFFDHE標準グループ
有限体 DH を実務で扱うなら、RFC 7919の標準FFDHEグループが基準になります。
ここで定義されたのは 5 種類で、TLS で交渉できるようコードポイントも割り当てられています。
独自の prime を各現場で抱えるのではなく、検討済みの標準 group を使うことで、由来不明のパラメータや弱い共有 prime を混ぜ込む余地を減らせます。
| グループ名 | ビット長 | コードポイント |
|---|---|---|
| グループ名 (RFC 7919) | ビット長 | コードポイント |
| ffdhe2048 | 2048 | 256 |
| ffdhe3072 | 3072 | 257 |
| ffdhe4096 | 4096 | 258 |
| ffdhe6144 | 6144 | 259 |
| ffdhe8192 | 8192 | 260 |
この表から読み取れるのは、有限体 DH を使うなら「小さな鍵長で何となく済ませる」時代は終わったということです。
最低線はffdhe2048で、その上は必要な安全度やポリシーに応じて選びます。
ただし、ビット長を上げるほど計算負荷も増えるため、Web の一般的な HTTPS ではECDHEが第一選択になりやすく、FFDHE は互換性や方針上の理由がある場面で使われる、という位置づけが自然です。
筆者の見方では、RFC 7919の価値は単に 5 個の group 名を覚えることではありません。
「有限体 DH を使うなら、勝手に決めた group ではなく標準化済みの土台に乗る」という運用規律を与えた点にあります。
TLS 1.3 の世界観は ECDHE 中心ですが、FFDHE が不要になったわけではなく、残すなら雑に残してはいけない、という整理です。
Logjamは歴史的な脆弱性として語られがちですが、実務者にとっての教訓は今も生きていて、グループ選択を設定の末尾の小さな項目として扱えなくなった、その一点に尽きます。
まとめ
要点再掲
ディフィー・ヘルマンは暗号化そのものではなく、離れた相手と同じ秘密を作るための鍵共有です。
安全性の芯は離散対数問題にあり、この非対称性があるから公開値を見られても共有秘密までは届きません。
ただし単体では相手確認をしないため、中間者攻撃を防げません。
現代の実装では認証と KDF を組み合わせた(E)DHEとして使われ、TLSSSHIPsecの土台になっています。
とくにTLS 1.3は forward secrecy を前提に組まれており、実務の主流はECDHEです。
次のアクション
情報セキュリティ企業での暗号実装検証を経て、暗号理論の解説に専念。公開鍵暗号からポスト量子暗号まで、数学的原理をわかりやすく伝えます。
関連記事
共通鍵暗号と公開鍵暗号の違い|図解で仕組み比較
ブラウザでHTTPSのサイトを開いた瞬間、画面には見えないところで「いま誰と鍵を決めたのか」と「その後の本文をどの鍵で守るのか」が一気に走ります。この記事では、まず共通鍵暗号の仕組みと量子コンピュータ時代に何が変わるかの節を先に参照すると、以降の議論の流れがつかみやすくなります。
AES暗号とは?歴史・仕組み・GCMまで
WebをHTTPSで開き、Wi‑Fiに接続し、ノートPCのディスク暗号化を有効にする。ふだん何気なく触れているこの3つの動作の奥には、同じ名前の暗号がいます。
公開鍵暗号の仕組みとRSAの原理図解
ブラウザの錠前アイコンを開いて証明書の詳細をのぞくと、Public-Key: RSA 2048 と Exponent: 65537 が並んでいて、公開鍵暗号は教科書の中だけの話ではなく、いま目の前の通信を支える現役技術なのだと実感します。
RSA暗号とは?素因数分解と公開鍵の仕組み
1977年に公開されたRSAは、公開してよい鍵(n, e)と外に出してはいけない秘密鍵dを分けることで、暗号と署名の考え方を一段進めた方式です。公開鍵暗号を数式から理解したい人、仕組みは知っているのに実務での役割が曖昧な人に向けて、歴史的位置づけから手で追える計算例までを一本につなげます。