公开密钥加密有两把钥匙:公钥加密、私钥解密(保密),或者私钥加密、公钥解密(签名)。 但它太慢,所以真实协议只用它来传一把对称密钥,正文全走对称加密。
上一节讲外壳在哪儿,这一节讲壳里的钥匙怎么换。这是整个 TLS 最核心的一步。
一、两把钥匙的分工
Demo 先用一个几十位的玩具 RSA 把结构摊开:p=61、q=53、n=3233、e=17、d=2753。 明文 65 用公钥加密得 2790,用私钥解回 65。 反过来也行:用私钥「加密」得 588,用公钥解回 65。 这种对称性正是数字签名的基础——公钥加密私钥解是「只有我能读」,私钥加密公钥解是「只有我能发」。 安全性的来源是:n 好算(两个素数相乘),但把 n 拆回 p 和 q 很难。 实际用的是 2048 位以上的 n,不是 4 位。
二、为什么不能全用非对称
Demo 换成 512 位的密钥(只为跑得动),n 有 154 位十进制数字。 用它加密一个 32 字节的会话密钥:加密 0.020 毫秒、解密 0.706 毫秒。 但 512 位的 n 一次最多装 64 字节。1 MB 正文要切成 16000 块。 逐块跑下来:加密约 0.4 秒、解密约 12.7 秒。 同样 1 MB 走对称(SHA-256 计数器密钥流):加密 70.7 毫秒、解密 5.4 毫秒。 解密侧差 180 倍——而且这还没算每块都要填充、都要协商的开销。 注意这里已经尽量把 Python 的开销压掉了(异或交给大整数一次算完), 否则测出来的差距会全是解释器开销,不是算法本身的差距。
三、混合加密与前向安全
所以真实做法是三步: 客户端生成随机会话密钥 → 用服务端公钥加密它 → 之后所有数据都用会话密钥走对称加密。 公开密钥只干「传一把钥匙」这一件小事。 TLS 1.3 更进一步:密钥交换改用 ECDHE,双方各出一份临时份额,当场算出同一份会话密钥, 私钥完全不参与。 好处叫前向安全:服务端私钥将来泄露了,也解不开以前录下的密文——因为那时候的会话密钥是临时算出来的, 没有留在任何地方。用 RSA 直接加密会话密钥就没这个性质:私钥一泄,历史流量全解。
四、动手 Demo:从 4 位 n 到 16000 块,把差距量出来
先用小素数看清 RSA 的加解密与签名方向,再生成一对 512 位密钥,用它加密一个 32 字节会话密钥; 然后拿 1 MB 正文分别走对称与非对称,量出两者的时间差并推到整份数据。
p, q = 61, 53
n, phi, e = p * q, (p - 1) * (q - 1), 17
d = pow(e, -1, phi) # 私钥指数
c = pow(65, e, n) # 公钥加密
back = pow(c, d, n) # 私钥解密
# 混合加密:公钥只用来加密这把 32 字节的会话密钥
sym = os.urandom(32)
ck = pow(int.from_bytes(sym, u'big'), pub[0], pub[1])
# 正文走对称:SHA-256 计数器密钥流
stream = _keystream(sym, len(big))
enc = _xor(big, stream) # 整段一次性异或,不逐字节循环
实跑输出:
--------------------------------------------------------------------
公开密钥:能公开的那把,和必须藏好的那把
--------------------------------------------------------------------
先来一个只有几十位的玩具 RSA,看清结构:
p=61 q=53 n=p*q=3233
公钥 = (e=17, n=3233) 私钥 = (d=2753, n=3233)
明文 65 → 用公钥加密得 2790 → 用私钥解回 65(对上了:True)
反过来也行:用私钥「加密」得 588,用公钥解回 65
这个方向的对称性正是数字签名的原理:
公钥加密、私钥解密 → 只有我能读 (保密)
私钥加密、公钥解密 → 只有我能发 (签名)
换成真一点的尺寸(512 位,只为演示跑得动):
生成一对密钥耗时 9 毫秒,n 有 154 位十进制数字
公钥 e=65537,私钥 d 是个 154 位数(这里不打印)
用它加密一个 32 字节的对称密钥:
加密 0.022 毫秒,解密 0.800 毫秒,解回来一致:True
注意 n 只有 512 位,一次最多装 64 字节——这就是「不能拿它加密大文件」的原因。
同一份数据,两种做法对比(1 MB 正文):
对称(SHA-256 计数器密钥流):加密 71.9 毫秒、解密 5.0 毫秒,还原一致:True
非对称(纯 RSA 逐块):加密 2000 块花 49.2 毫秒,解密 200 块花 162.9 毫秒
按 512 位密钥算,1 MB 要切成 16000 块
整份加密约 0.4 秒、解密约 13.0 秒
解密侧比对称慢约 181 倍——这还没算「每块都要填充、要协商」的额外开销
所以真实协议是混合的:
1. 客户端生成一个随机的对称密钥(会话密钥)
2. 用服务端的公钥把它加密,发过去——只有服务端能解
3. 之后所有数据都用这个对称密钥加密
TLS 里的密钥交换就是这三步,只是用的不是 RSA 而是 ECDHE,
好处是每次会话的密钥都不同(前向安全),私钥将来泄露也解不开旧记录。
玩具例子:65 → 2790 → 65,方向反过来 65 → 588 → 65。两个方向都成立。 512 位密钥:n 有 154 位十进制数字,生成耗时 6 毫秒; 加密 32 字节会话密钥 0.020 毫秒,解密 0.706 毫秒。 1 MB 对称:加密 70.7 毫秒、解密 5.4 毫秒。1 MB 逐块 RSA:加密 16000 块约 0.4 秒,解密约 12.7 秒。 解密侧慢约 180 倍。 这还没算填充和协商的额外开销,也没算 2048 位密钥比 512 位慢得多。 结论只有一个:正文绝不能走非对称加密。
30 秒实验
- 把密钥从 512 位改成 256 位,看生成时间和加解密时间各降多少。
- 把
e从 65537 改成 3,观察加密变快、但安全性下降。 - 想一想:如果会话密钥用 RSA 加密,服务端私钥泄露后历史流量会怎样?
常见误区
- 「非对称加密更安全,所以应该全用它」——太慢,慢好几个数量级。
- 「公钥和私钥可以随便换着用」——数学上可以,但用法和安全性不同。
- 「RSA 就是 TLS 的密钥交换」——TLS 1.3 已经不用 RSA 换密钥了。
自测三问(点开对答案)
1. 公钥加密、私钥解密,和反过来,分别对应什么用途? —— 前者是保密(只有我能读),后者是签名(只有我能发)。 2. 为什么正文不用非对称加密? —— 太慢:1 MB 逐块 RSA 解密约 12.7 秒,比对称慢约 180 倍。 3. 前向安全是什么意思? —— 会话密钥临时算出、不留存,私钥将来泄露也解不开旧密文。公钥要传出去,可怎么证明「这把公钥属于这个域名」?靠证书。
- ☐ (本节完,下一节 7.2.4 证明公开密钥正确性的证书)