
ディフィー・ヘルマン鍵交換をPythonで実装|"盗聴されても"共通鍵を作る
2026-09-11 ・ 実践
AESのような共通鍵暗号には「同じ鍵をどう安全に共有するか」という難問があります。その答えの一つが ディフィー・ヘルマン(DH)鍵交換。通信を全部盗聴されても、共通の秘密鍵を作れる魔法のような手法です。Pythonで実装して驚きを体感しましょう。
発想:混ぜた色は分けられない
DHはよく「絵の具」でたとえられます。共通の色に、各自が秘密の色を混ぜて交換 → 受け取った色に自分の秘密をもう一度混ぜる。すると2人だけが同じ色にたどり着く。混ぜるのは簡単、分離は困難という非対称性が安全の源です。
公開のやり取りだけで秘密を共有
共通の素材
公開してOK
秘密を混ぜる
各自バラバラ
同じ鍵に到達
2人だけ
準備
Python標準ライブラリだけ(数式で本質を見ます)。
① 数式版DHを最小実装
数学では「絵の具」の代わりに**べき乗の余り(離散対数の困難性)**を使います。
# 公開パラメータ(全員が知ってOK)
p = 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1 # 大きな素数
g = 2 # 生成元
import secrets
# アリスとボブがそれぞれ秘密の数を用意
a = secrets.randbelow(p - 2) + 1 # アリスの秘密
b = secrets.randbelow(p - 2) + 1 # ボブの秘密
# 公開値を計算して交換(これは盗聴されてOK)
A = pow(g, a, p) # アリス → ボブ
B = pow(g, b, p) # ボブ → アリス
# 相手の公開値に自分の秘密を混ぜる
alice_key = pow(B, a, p)
bob_key = pow(A, b, p)
print("鍵が一致?:", alice_key == bob_key)
出力は 鍵が一致?: True。公開値 A, B を盗聴者が見ても、秘密の a, b がわからなければ共通鍵は計算できません。これがDHの核心です。
盗聴者にできないこと
盗聴者は g, p, A, B を全部見られます。でも A=gᵃ から a を逆算するのが(大きな素数では)現実的に不可能。この「離散対数問題」の難しさが、鍵を守っています。
② 実務では鍵を派生させる
共有した数値はそのまま鍵に使わず、ハッシュで固定長の鍵にします。
import hashlib
shared = alice_key.to_bytes((alice_key.bit_length() + 7) // 8, "big")
aes_key = hashlib.sha256(shared).digest() # AES用の256bit鍵に
print("AES鍵:", aes_key.hex()[:32], "...")
これでAES-GCMの鍵として使えます。
中間者攻撃に注意
DHは「盗聴」には強いですが、通信に割り込む中間者攻撃(なりすまし)は単体では防げません。実際のTLSでは、DHにデジタル署名を組み合わせて相手が本物か確かめます。現代のTLSは前方秘匿性のためDHを標準採用しています。
まとめ
- DH鍵交換は、公開のやり取りだけで共通の秘密鍵を作れる
- べき乗の余り(離散対数の困難性)が安全の源
- 共有値はハッシュで固定長の鍵に派生させて使う
- 盗聴には強いが、中間者攻撃には署名との併用が必要
もう少し詳しく(背景と理論)
ディフィー・ヘルマン鍵交換は Diffie & Hellman (1976) が公開鍵暗号の概念とともに発表した、暗号史の転換点です1。安全性は離散対数問題の困難性に依存します。単体では通信に割り込む中間者攻撃(MITM)を防げないため、実際のTLSでは署名で相手を認証して併用します2。現代は計算効率と鍵長の面で**楕円曲線版(ECDH)が主流で、セッションごとに使い捨ての鍵を使う ephemeral(DHE/ECDHE)により、後に長期鍵が漏れても過去の通信を守る前方秘匿性(forward secrecy)**を実現します3。なお素数やパラメータの選択を誤ると Logjam のような攻撃を受けるため、標準化された安全なパラメータを使うのが鉄則です。
次の一歩 🌸
作った鍵で暗号化するAES実装、なりすまし対策のRSA署名、通信全体のHTTPSのしくみへどうぞ。