2. TLS握手协议:密钥交换与身份认证
握手协议,说白了就是客户端和服务器在正式加密通信之前,先互相打个招呼、亮明身份、并商量好怎么加密。我参与过不少安全审计项目,发现握手阶段出问题的比例相当高——证书过期、密钥协商失败、甚至中间人攻击。所以这一章,咱们把密钥交换和身份认证这两个核心掰开揉碎了讲。
2.1 密钥交换:不是所有算法都「安全」
先问一个问题:为什么不能直接靠公钥加密对称密钥?嗯,这就要说到「前向安全性」(Forward Secrecy)了。你用RSA密钥交换时,客户端生成一个随机串然后用服务器公钥加密发过去。万一服务器私钥泄露了呢?之前所有会话记录都能被解开。我就见过一个金融公司因为旧RSA私钥被拖库,结果几个月的历史流量全被解密——这就是为什么现在大家更倾向用DHE或ECDHE。
常见的密钥交换算法对比如下:
| 算法 | 前向安全 | 是否依赖RSA/EC证书 | 计算开销 | 实际使用场景 |
|---|---|---|---|---|
| RSA密钥交换 | ❌ 否 | 是 | 低(客户端加密) | 仅兼容老旧设备 |
| DH(静态参数) | ❌ 否(参数固定) | 否(需要签名) | 中(模幂运算) | 极少使用 |
| DHE(临时DH) | ✅ 是 | 是(签名验证参数) | 高 | 必须要求安全时选用 |
| ECDHE | ✅ 是 | 是(签名验证参数) | 低(椭圆曲线) | 现代首选 |
2.2 身份认证:证书链与数字签名
密钥交换之前,必须先确认对方是谁。这就是证书的作用。服务器在Hello之后发一个Certificate消息,里面是X.509证书链。客户端收到后要做三件事:
- 验证证书有效期:是不是在有效期内?(我曾见过证书过期半年还在跑的服务,用户必须手动忽略错误才能访问)
- 验证签名:用上级CA的公钥解密签名,对比证书摘要。
- 验证证书撤销状态:检查CRL或OCSP,这一步很多人漏掉。
自签名证书在内部测试时可以用,但生产环境别这么干——否则每个客户端都要手动信任你的根证书,一旦泄露就是灾难。
2.3 握手流程中的密钥交换与认证(以ECDHE为例)
我们看一个最简单的TLS 1.2握手(ECDHE + RSA签名)。客户端和服务器各做什么?
- ClientHello:客户端发一个随机数(client_random)、支持的密码套件、TLS版本。
- ServerHello:服务器选一个密码套件(比如
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256),发自己的随机数(server_random)。 - Certificate:服务器发自己的证书(含公钥)。
- ServerKeyExchange:服务器生成临时ECDH私钥,计算公钥,然后用RSA证书私钥对公钥签名。这一步是关键——既协商了临时密钥参数,又用证书证明了这个参数是服务器发的。
- ServerHelloDone:服务器说“我说完了”。
- ClientKeyExchange:客户端验证签名后,也生成临时ECDH密钥对,把自己的公钥发给服务器。此时双方都有对方公钥,可以计算共享密钥(pre-master secret)。
- ChangeCipherSpec + Finished:双方告知“后面要用加密通信了”,然后发一个用新密钥加密的Finished消息,包含之前所有握手消息的摘要——这就是最终确认,防止中间人篡改。
你注意到没有?在第4步里,服务器的身份认证是通过“用证书私钥签名临时公钥”完成的。所以证书本身并不直接参与密钥交换,而是用来担保临时参数的真实性。
signature_algorithm字段。
2.4 图表:TLS 1.2 ECDHE握手中的密钥交换与认证
下面这张图浓缩了最核心的流程,请结合上面步骤一起看:
2.5 主密钥与会话密钥的派生
Pre-Master Secret 算出来之后,双方用 PRF(伪随机函数)结合 client_random 和 server_random,生成 master secret。然后用 master secret 继续派生加密密钥、HMAC 密钥、IV 等。这个机制保证即使同一个 pre-master secret 被重用(比如客户端重连时复用会话),也会因为随机数不同而产生不同的会话密钥。
我记得有一次调试一个奇怪的问题:客户端重连时老是握手失败。后来才发现服务器把 session ID 当成了会话复用的唯一标识,但忘记了更新预主密钥——这直接导致派生出来的密钥对不上。所以我说,别自己实现TLS状态机,踩坑概率太高,用成熟库吧。