2. TLS握手协议:密钥交换与身份认证

握手协议,说白了就是客户端和服务器在正式加密通信之前,先互相打个招呼、亮明身份、并商量好怎么加密。我参与过不少安全审计项目,发现握手阶段出问题的比例相当高——证书过期、密钥协商失败、甚至中间人攻击。所以这一章,咱们把密钥交换和身份认证这两个核心掰开揉碎了讲。

2.1 密钥交换:不是所有算法都「安全」

先问一个问题:为什么不能直接靠公钥加密对称密钥?嗯,这就要说到「前向安全性」(Forward Secrecy)了。你用RSA密钥交换时,客户端生成一个随机串然后用服务器公钥加密发过去。万一服务器私钥泄露了呢?之前所有会话记录都能被解开。我就见过一个金融公司因为旧RSA私钥被拖库,结果几个月的历史流量全被解密——这就是为什么现在大家更倾向用DHE或ECDHE。

常见的密钥交换算法对比如下:

算法前向安全是否依赖RSA/EC证书计算开销实际使用场景
RSA密钥交换❌ 否是低(客户端加密)仅兼容老旧设备
DH(静态参数)❌ 否(参数固定)否(需要签名)中(模幂运算)极少使用
DHE(临时DH)✅ 是是(签名验证参数)高必须要求安全时选用
ECDHE✅ 是是(签名验证参数)低(椭圆曲线)现代首选
💡 我的建议: 能上ECDHE就别用RSA密钥交换。有一次我帮客户做安全评估,他们的服务端只支持RSA,我直接建议升级TLS库——前向安全不是选项,是底线。

2.2 身份认证:证书链与数字签名

密钥交换之前,必须先确认对方是谁。这就是证书的作用。服务器在Hello之后发一个Certificate消息,里面是X.509证书链。客户端收到后要做三件事:

  • 验证证书有效期:是不是在有效期内?(我曾见过证书过期半年还在跑的服务,用户必须手动忽略错误才能访问)
  • 验证签名:用上级CA的公钥解密签名,对比证书摘要。
  • 验证证书撤销状态:检查CRL或OCSP,这一步很多人漏掉。

自签名证书在内部测试时可以用,但生产环境别这么干——否则每个客户端都要手动信任你的根证书,一旦泄露就是灾难。

避坑指南: 我曾经遇到一个情况:服务器发来的证书链中间缺了一张CA证书。客户端因为找不到信任锚点直接报错。解决方案很简单——在服务器配置里把完整的证书链(含中间CA)一起发过去就OK了。

2.3 握手流程中的密钥交换与认证(以ECDHE为例)

我们看一个最简单的TLS 1.2握手(ECDHE + RSA签名)。客户端和服务器各做什么?

  1. ClientHello:客户端发一个随机数(client_random)、支持的密码套件、TLS版本。
  2. ServerHello:服务器选一个密码套件(比如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256),发自己的随机数(server_random)。
  3. Certificate:服务器发自己的证书(含公钥)。
  4. ServerKeyExchange:服务器生成临时ECDH私钥,计算公钥,然后用RSA证书私钥对公钥签名。这一步是关键——既协商了临时密钥参数,又用证书证明了这个参数是服务器发的。
  5. ServerHelloDone:服务器说“我说完了”。
  6. ClientKeyExchange:客户端验证签名后,也生成临时ECDH密钥对,把自己的公钥发给服务器。此时双方都有对方公钥,可以计算共享密钥(pre-master secret)。
  7. ChangeCipherSpec + Finished:双方告知“后面要用加密通信了”,然后发一个用新密钥加密的Finished消息,包含之前所有握手消息的摘要——这就是最终确认,防止中间人篡改。

你注意到没有?在第4步里,服务器的身份认证是通过“用证书私钥签名临时公钥”完成的。所以证书本身并不直接参与密钥交换,而是用来担保临时参数的真实性。

⚠️ 容易被坑的地方: 有些老旧代码在验证ServerKeyExchange签名时,没有检查签名算法是否匹配证书里的密钥用途。攻击者可以用一个弱哈希算法绕过签名验证。我记得一个开源项目就因为这个漏洞被曝出后门——所以请务必校验signature_algorithm字段。

2.4 图表:TLS 1.2 ECDHE握手中的密钥交换与认证

下面这张图浓缩了最核心的流程,请结合上面步骤一起看:

TLS 1.2 握手(ECDHE + RSA证书)——密钥交换与身份认证 ClientHello 发送 client_random + 密码套件 验证证书 & 签名 检查链、有效期、撤销状态 ClientKeyExchange 发送临时 ECDH 公钥 计算 Pre-Master Secret ECDH 乘算 → 共享密钥 ServerHello + Certificate 选套件,发 server_random + 证书 ServerKeyExchange 生成临时 ECDH 密钥对,签名公钥 验证客户端公钥 不是必须,但可检查曲线点合法性 计算 Pre-Master Secret ECDH 乘算 → 共享密钥 证书认证 密钥协商 密钥生成 ➡ 签名保证了临时公钥确实来自持有证书私钥的服务器 ➡ ECDH 各自生成本地私钥,计算一次共享密钥,用完即弃

2.5 主密钥与会话密钥的派生

Pre-Master Secret 算出来之后,双方用 PRF(伪随机函数)结合 client_random 和 server_random,生成 master secret。然后用 master secret 继续派生加密密钥、HMAC 密钥、IV 等。这个机制保证即使同一个 pre-master secret 被重用(比如客户端重连时复用会话),也会因为随机数不同而产生不同的会话密钥。

我记得有一次调试一个奇怪的问题:客户端重连时老是握手失败。后来才发现服务器把 session ID 当成了会话复用的唯一标识,但忘记了更新预主密钥——这直接导致派生出来的密钥对不上。所以我说,别自己实现TLS状态机,踩坑概率太高,用成熟库吧。

2.6 小结