“如果敌人正在窃听所有对话,我们如何才能创造一个只属于你我的秘密?”
1976年,两位天才对这个看似矛盾的问题给出了答案。
>
>
本文涵盖内容
- Diffie-Hellman (DH)出现的历史背景
- 通过油漆比喻理解其直观工作原理
- 模幂运算和离散对数问题的数学基础
- 使用Python代码亲手实现密钥交换
- 中间人攻击 (MITM)及其向ECDH的演进
- 在TLS·SSH等实际环境中的应用
引言 — 如何在见面之前共享秘密密钥
出乎意料的是,加密最大的难题并非“加密算法”本身。真正困难的是如何安全地共享用于加密的密钥。
1976年以前,两个人若要进行秘密通信,必须事先亲自见面共享相同的密钥,或者通过可信的信使(courier)传递写有密钥的纸张。您可以联想到冷战时期的谍战电影。
然而,在互联网时代该如何操作呢?从未谋面的服务器和客户端,如何在所有通信都可能被窃听的公共网络上,创建出只属于他们两人的秘密密钥呢?
解决这个看似矛盾的问题的,正是由Whitfield Diffie和Martin Hellman提出的Diffie-Hellman密钥交换算法。这项发明奠定了现代互联网安全的基础,并于2015年为两人赢得了图灵奖。

油漆比喻 — 5分钟理解DH
在深入数学原理之前,我们先来看一个最直观地解释DH的油漆比喻。
- 爱丽丝和鲍勃公开约定一种黄色油漆。敌人也能看到。
- 爱丽丝将她自己的秘密颜色(红色)混入黄色中,制成橙色,然后发送给鲍勃。
- 鲍勃也将他自己的秘密颜色(蓝色)混入黄色中,制成绿色,然后发送给爱丽丝。
- 爱丽丝将她收到的绿色与她自己的秘密颜色(红色)混合 → 黄+蓝+红
- 鲍勃将他收到的橙色与他自己的秘密颜色(蓝色)混合 → 黄+红+蓝
结果,两人都得到了相同的颜色。然而,窃听者只看到了黄色、橙色和绿色。由于从已经混合的油漆中分离出原始的秘密颜色实际上是不可能的,窃听者无法得知最终的秘密颜色。
这种“易于混合但难以分离”的单向性是DH的本质。
数学原理 — 模幂运算与离散对数问题
将油漆比喻转化为数学,如下所示:
公开参数 (所有人都知道的值)
- 大素数 p
- p 的原根 g (生成元, generator)
密钥交换步骤
- 爱丽丝选择一个秘密值 a,计算公开值 A = g^a mod p,并发送给鲍勃。
- 鲍勃选择一个秘密值 b,计算公开值 B = g^b mod p,并发送给爱丽丝。
- 爱丽丝计算 K = B^a mod p。
- 鲍勃计算 K = A^b mod p。
两人得到的值都精确地等于 g^(ab) mod p。这就是只有两人知道的共享秘密密钥 (shared secret)。
窃听者可以知道 p, g, A, B 的所有值,但必须从 A = g^a mod p 中反推出 a。这个问题被称为离散对数问题 (Discrete Logarithm Problem, DLP),对于足够大的 p(通常2048位以上),在现实时间内无法解决。
这与RSA的直观感受类似:乘法容易,但因式分解困难。DH利用了“指数计算容易,但其逆运算(离散对数)困难”的不对称性。
使用Python亲手实现
为了巩固直观理解,我们用小数字亲自操作一下。
# 教学示例 — 严禁用于实际生产环境
p = 23 # 公开素数 (实际应用中至少2048位)
g = 5 # 公开生成元
# 爱丽丝的秘密值
a = 6
A = pow(g, a, p) # g^a mod p
print(f"앨리스가 공개하는 값 A = {A}")
# 鲍勃的秘密值
b = 15
B = pow(g, b, p) # g^b mod p
print(f"밥이 공개하는 값 B = {B}")
# 各自计算共享秘密密钥
K_alice = pow(B, a, p) # B^a mod p
K_bob = pow(A, b, p) # A^b mod p
print(f"앨리스의 공유 키: {K_alice}")
print(f"밥의 공유 키: {K_bob}")
assert K_alice == K_bob
执行后,您会发现双方都得到了相同的密钥 2。
在实际应用中,不应自行实现,而应使用经过验证的库。以下是使用 cryptography 库执行安全DH密钥交换的示例:
from cryptography.hazmat.primitives.asymmetric import dh
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
# 1) 生成公开参数 (通常由服务器生成一次后重用)
parameters = dh.generate_parameters(generator=2, key_size=2048)
# 2) 爱丽丝、鲍勃各自生成密钥对
alice_priv = parameters.generate_private_key()
bob_priv = parameters.generate_private_key()
# 3) 交换公钥后导出共享秘密
shared_alice = alice_priv.exchange(bob_priv.public_key())
shared_bob = bob_priv.exchange(alice_priv.public_key())
assert shared_alice == shared_bob
# 4) 原始秘密不直接使用,而是通过KDF派生对称密钥
derived_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b"handshake data"
).derive(shared_alice)
这里有一个重要点是,DH生成的共享秘密(raw shared secret)不应直接用作对称密钥。由于其分布可能不均匀,因此必须通过HKDF等密钥派生函数(KDF)进行提炼后才能使用。
⚠️ 注意事项 — DH并非万能
1. 易受中间人攻击 (MITM)
DH不验证对方的身份。如果攻击者马洛里介入爱丽丝和鲍勃之间,会发生以下情况:
- 爱丽丝 ↔ 马洛里:建立共享密钥 K1
- 马洛里 ↔ 鲍勃:建立共享密钥 K2
- 马洛里用 K1 解密所有消息 → 检查/篡改内容 → 用 K2 重新加密后转发
因此,在实际应用中,必须通过数字签名、证书 (PKI)、预共享密钥等方式验证对方的身份。在TLS中,服务器证书就扮演了这一角色。
2. 弱参数是致命的
2015年发布的Logjam攻击利用了许多服务器使用512位短DH参数的弱点。此外,如果未使用足够随机的秘密值 a, b,则会暴露于猜测攻击。使用至少2048位以上的安全素数群(如RFC 7919的ffdhe群)是安全的。
3. 静态DH vs 临时DH (DHE)
静态DH是指一次性生成密钥对并持续重用,这意味着一旦秘密密钥泄露,过去的所有通信都将被解密。相比之下,DHE (Ephemeral DH)为每个会话生成新的密钥对,提供前向保密性 (Forward Secrecy)。这也是TLS 1.3只允许DHE或ECDHE的原因。
4. 对量子计算机脆弱
离散对数问题可以通过Shor算法在多项式时间内解决。如果足够强大的量子计算机出现,DH将被破解。NIST正在推动的后量子密钥交换 (PQC),特别是将ML-KEM(原Kyber)与DH结合的混合方式,正在成为下一代标准。
ECDH — 更小、更快、更强
在椭圆曲线上实现相同思想的便是ECDH (Elliptic Curve Diffie-Hellman)。
| 项目 | 传统DH | ECDH |
| 基础问题 | 离散对数 (DLP) | 椭圆曲线离散对数 (ECDLP) |
| 等效强度密钥大小 | 2048位 | 256位 |
| 运算速度 | 较慢 | 较快 |
| 带宽 | 较大 | 较小 |
通过更短的密钥实现相同的安全强度,ECDHE(临时ECDH)已成为现代TLS 1.3、SSH、Signal协议等几乎所有地方的标准。
✅ 总结 / 结束语
- Diffie-Hellman是第一个允许双方在公开信道上协商秘密密钥的公钥算法。
- 其核心原理是 g^(ab) mod p 的对称结果以及离散对数问题的计算难度。
- DH仅负责密钥交换,不验证对方身份。为确保安全,必须与签名·证书结合使用。
- 在实际应用中,使用临时密钥的DHE/ECDHE是标准,它负责TLS的前向保密性。
- 下一步,建议您了解TLS 1.3握手、Signal协议的X3DH以及抗量子密钥交换算法ML-KEM (Kyber)。

发表回复