4. 对称加密密钥是如何在TLS握手中协商生成的?

这个问题,我面试时几乎必问。
很多人能背出「客户端随机数 + 服务端随机数 + 预主密钥 = 主密钥 → 派生密钥」。
但坦白讲,能讲清楚 「为什么不能直接用 RSA 加密的预主密钥当对称密钥」 的人,我面了上百个,一只手数得过来。

今天咱们就掰开揉碎讲明白。
你想想看,握手里所有公钥操作,最终目的只有一个——

让客户端和服务端在不可信的信道上,安全地算出同一组对称密钥。

而且这个推导过程必须是 单向的:即便攻击者拿到了所有握手消息,也无法逆向算出最终的密钥。

4.1 密钥协商的三个原料

我用一个最简单的比喻:做蛋糕。
客户端和服务端各自往配方里加料,最后烤出同一个蛋糕。

  • 客户端随机数 (Client Random) —— 32 字节,明文发给服务端。它保证每次握手的密钥都不一样。
  • 服务端随机数 (Server Random) —— 32 字节,明文发给客户端。同上。
  • 预主密钥 (Pre-Master Secret) —— 48 字节。这是保密的。它才是真正的基础秘密。

但在不同密钥交换算法下,预主密钥的传递方式天差地别。

4.2 传统 RSA 密钥交换(现在应该避坑)

说实话,我在 2017 年排查过一个客户问题,他们还在用 RSA 密钥交换,刚升级 TLS 1.2 就报错。我一查日志,服务端证书是 RSA 公钥,但协商的密码套件里没有 RSA key exchange——现代服务端普遍默认禁用。
嗯,这是个典型的兼容性陷阱。

RSA 的流程很简单:

  1. 客户端生成 48 字节的预主密钥(前 2 字节是 TLS 版本号,后面 46 字节是随机数)。
  2. 用服务端证书里的 RSA 公钥加密 premaster,发给服务端。
  3. 服务端用私钥解密,拿到完整的 premaster。

然后双方拿 Client Random + Server Random + Pre-Master Secret 按 PRF (Pseudo-Random Function) 算出一个 48 字节的 Master Secret。

Master Secret = PRF(Pre-Master Secret, "master secret",
                    ClientRandom + ServerRandom)[0..47]

但 RSA 交换有个致命缺陷:没有前向安全性。
如果私钥泄露,攻击者可以解密历史上所有会话的记录。
所以我个人的建议:新系统不要再使用 RSA 密钥交换。 很多合规审计(比如 PCI DSS)现在也明确要求禁用。

4.3 现代方案:DHE / ECDHE 密钥交换

这才是主流。它解决了前向安全性问题。
我记得有一次线上故障,客户从国外回放流量做安全审计,所有旧会话都能被解密,因为私钥定期轮转周期设得太长——ECDHE 配合 1 小时会话票证轮转,才把审计人员的嘴堵上。

DH 的过程你肯定熟悉:

  • 客户端生成临时 DH 私钥,发公钥(Client DH Public Key)
  • 服务端也生成临时 DH 私钥,发公钥(Server DH Public Key)
  • 双方各自计算 DH 共享秘密——这就是 Pre-Master Secret

注意:这里的 DH 私钥是临时生成的,用完即弃。所以即使未来私钥泄露,也无法解密过去的会话。

然后 PRF 计算 Master Secret 的公式一模一样:

Master Secret = PRF(Pre-Master Secret, "master secret",
                    ClientRandom + ServerRandom)[0..47]

看到没?PRF 的输入里,两个随机数都是明文,只有 Pre-Master Secret 是秘密。这是整个安全的基础。

4.4 从 Master Secret 到工作密钥(密钥派生)

拿到 Master Secret 之后,并不是直接拿它加密。还得再次用 PRF 派生出多个子密钥。这在 TLS 1.2 里称为「密钥扩展」。

我面试时常问:Master Secret 长度是 48 字节,为什么不能直接当 AES-256 的密钥?
原因有二:一是需要多个密钥(加密、MAC、IV);二是不同方向要用不同密钥,防止重放。

派生的产物通常包括(以 AES-128-GCM 为例):

参数 长度 用途
Client Write Key 16 字节 客户端加密 → 服务端解密
Server Write Key 16 字节 服务端加密 → 客户端解密
Client Write IV 4 字节(隐式) 用于 GCM 模式计数器初始化
Server Write IV 4 字节(隐式) 同上

关键:客户端和服务端的密钥是不同的。 如果两边用同一个密钥,攻击者可以伪造数据包发送给被攻击方——这叫 反射攻击。

在 TLS 1.3 里,密钥派生做了简化。Master Secret 被直接称为 Traffic Secret,并通过 HKDF (Extract-and-Expand) 一步到位算出握手密钥和应用密钥,不再显式区分 Master Secret。但底层理念一样:

  • 用随机数打散
  • 用密钥协商结果做种子
  • 输出不同用途的独立子密钥

4.5 核心流程图

我用一张图总结整个密钥协商的生命周期:

TLS对称密钥协商流程图 TLS 1.2 对称密钥协商全流程 客户端随机数 (明文) 服务端随机数 (明文) 预主密钥 (保密) PRF(三个输入) → Master Secret (48B) master secret 标签 + 种子 密钥扩展 (再次 PRF) Client/Server Write Key + IV Client Write Key Server Write Key Client Write IV Server Write IV 所有密钥都独立且单向:客户端和服务端永远不会使用相同的密钥加密同一方向的数据

4.6 面试常问的坑(我踩过的那种)

  • 「Master Secret 长度为什么是 48 字节?」 —— 因为 TLS 1.0/1.1 时代,密钥派生需要足够的熵,48 字节刚好覆盖多数密码套件(DES、3DES、AES-128/256 等)。现在其实可以更短,但为了兼容性没改。
  • 「如果客户端随机数和服务端随机数相同会怎样?」 —— 风险很大。两边会算出一样的 Master Secret,但方向不同的工作密钥仍然不同(因为派生时有 client/server 标识)。但随机数由双方分别生成,实践中不会相同。
  • 「预主密钥泄漏了会怎样?」 —— 整个会话就废了。因为两个随机数是明文,攻击者可以算出 Master Secret 和所有工作密钥。我遇到过某云厂商把 premaster 打印到日志里……嗯,那是个高危漏洞。

避坑: 我曾经在生产环境看到过这么一个问题——客户端生成 premaster 时,前 2 字节(版本号)写死为 0x0303(TLS 1.2),但在 TLS 1.3 协商时,服务端强行解读为 TLS 1.2,导致版本回滚攻击。后来我强制在 premaster 里校验版本号与协商版本一致,才通过安全审计。

4.7 小结

对称密钥的协商,本质上是一个 「公开组件 + 秘密种子 → 单向函数 → 独立子密钥」 的过程。
公开组件(随机数)防止重放;秘密种子(premaster)保证机密;PRF 保证推导不可逆;多个子密钥保证方向独立。

面试时,如果能把 PRF 的输入输出讲清楚,再随口带一个「RSA 没有前向安全性、ECDHE 推荐、密钥派生方向独立」的对比,面试官大概率会点头。