TLS 握手
TLS 握手为已有的 TCP 连接建立加密与身份认证。本课展示 TLS 1.3 的一次完整握手:双方交换公钥份额,各自推导相同秘密,服务端再对本次握手摘要签名,证明自己持有证书对应的私钥。正常流程在 TCP 建连后增加一个 RTT。
可以把密钥协商想成混色:双方从公开的黄色开始,各加一种私有颜色,交换混合色,再加入自己的私有颜色,得到同一结果。旁观者只见到混合色。这个比喻解释 Diffie–Hellman 协商,却没有回答对方是谁;身份认证正是本课的重点。
TLS 握手如何工作
客户端选私有值 a,只发送 A = g^a mod p;服务端选 b,只发送 B = g^b mod p。客户端计算 B^a mod p,服务端计算 A^b mod p,两者都等于 g^(ab) mod p,记为 S。a、b、S 都不传输。合适的真实参数让从公有值恢复秘密在计算上不可行;本课的小数值可以破解,只用于检查算术。
双方把 S 与握手记录送入 HKDF 密钥派生流程,得到握手密钥。ServerHello 之后的握手内容被加密,包括证书。记录首部仍可见,兼容用的 ChangeCipherSpec 也不是加密握手内容,不能理解为线路上所有字节都被隐藏。
Certificate 提供经证书链认证的身份和公钥,说明该公钥对应 example.com。证书是公开资料,复制一张有效证书不能证明持有私钥。
CertificateVerify 使用证书对应的私钥,对带上下文的本次握手记录摘要签名。记录包含双方 key share,因此其他连接的签名不能直接移用。验证通过才说明当前对端持有相应私钥。证书验证和握手签名验证缺一不可。
双方最后发送 Finished:用派生出的 Finished 密钥,对截至该消息之前的握手记录摘要计算 HMAC,并非直接用 S 作 HMAC 密钥。若中途修改未加密的 hello,双方记录不同,验证就会失败。TLS 还另有版本降级保护机制,不能把所有降级防护都归为这一个检查。
TLS 握手逐步演示
默认 full handshake 连接 example.com:443,TCP 已建立。使用 p = 23、g = 5,便于手算。
- 初始状态。 TCP 已通,但尚无 TLS 密钥;此前的字节可被路径上的观察者读取。
- 客户端生成份额。 私有
a = 6,公开A = 5^6 mod 23 = 8。私有值旁有锁,不会出现在数据包中。 - ClientHello 明文发送。 携带
key_share A = 8和 SNI 主机名。本例未使用 ECH。真实参数难以由 A 反推 a,小数值不具备这种安全性。 - 服务端生成份额。 私有
b = 15,公开B = 5^15 mod 23 = 19。两个临时私有值为本连接生成,用后丢弃。 - 服务端推导 S。
A^b mod 23 = 8^15 mod 23 = 2。它没有接收秘密,却已能派生密钥。 - ServerHello。 发送
key_share B = 19。这是本流程最后一个未加密的握手消息,不意味着记录首部也从此不可见。 - 客户端推导 S。
B^a mod 23 = 19^6 mod 23 = 2。两边结果相同,S 从未传输。图中阴影标出后续加密的握手内容。 - Certificate。 画布以
CN = example.com简写身份;真实主机名验证使用 subjectAltName,而非 CN。证书携带服务端公钥,链可到达受信 CA。加密传输提供隐私,不改变证书内容本身可公开的性质。 - CertificateVerify。 服务端用证书对应私钥签署本次握手记录摘要及规定的上下文。
- 服务端 Finished。 用派生的服务端 Finished 密钥计算握手摘要 HMAC。
- 三项证书检查。 检查链是否受信、名称是否匹配、是否在有效期内。全部通过,但身份验证尚未结束。
- 验证签名。 简化表达式
verify(cert.pub, sig, H(transcript))返回 true。证书说明公钥身份,签名证明当前对端持有私钥。仅复制证书会在这里失败。 - 验证握手记录。 客户端按自己的记录重新计算 Finished HMAC。匹配说明此前记录在此认证下保持一致。
- 客户端 Finished。 返回确认,双方完成握手。
- 应用密钥。 HKDF 派生另一组应用流量密钥,HTTP 请求不使用握手流量密钥加密。
- 发送
GET / HTTP/1.1。 请求已加密,距离 TCP 建连增加一个 RTT。 - 查看结果。 画面计数为七次传输、一次握手签名验证,未发送私钥或共享秘密。七次包含应用请求,并省略 EncryptedExtensions,不是实际 TLS 握手消息总数。旁观者看到
A = 8、B = 19、地址、时间、包大小及本例的 SNI,看不到请求内容。
可用以下命令观察真实握手。OpenSSL 自己是通信端点,因此能解码显示加密记录中的握手消息;这不同于没有密钥的被动抓包。
openssl s_client -connect example.com:443 -tls1_3 -msg
为什么有效证书还需要握手签名
选择 On-path attacker。攻击者控制中间路由器,可以改写或转发消息。若只转发原消息,客户端与服务端会协商出攻击者不知道的密钥,所以攻击者替换 key share。
攻击者使用 m = 9,M = 11:保留客户端的 A = 8,向服务端发送 M,再用 M 替换发给客户端的 B。于是客户端一侧得到 M^a mod 23 = 9,服务端一侧得到 M^b mod 23 = 10。攻击者分别与双方共享秘密,客户端与服务端却没有共同秘密。
该场景第 11 步中,两次密钥推导都成功,尚未认证的加密通道没有识别攻击者。Diffie–Hellman 保证与某个对端协商密钥,本身不保证对端身份。
攻击者原样转发服务端证书,链、名称、日期都通过。到了 CertificateVerify,服务端签名覆盖的是 H(A=11 · B=19),客户端验证的却是 H(A=8 · B=11)。记录不同,签名不成立;攻击者又没有服务端私钥,无法制作正确签名。客户端发出 decrypt_error 并终止连接。浏览器可能报告握手失败,不能把任意酒店网络的证书警告都当成这种攻击证据。
若协议完全去掉这项身份绑定,攻击者就能分别处理两条连接的认证码并转发应用数据。证书公开不是漏洞,缺少对本次协商的持钥证明才会让这里的替换成立。
会话恢复省略了什么
选择 Session resumption。浏览器保存上次连接的票据与关联的 PSK。ClientHello 提供票据标识,并带上使用关联秘密派生密钥计算的 binder,认证省略 binder 值后的 ClientHello 记录。票据标识可见,单独复制它不能证明知道 PSK。
服务端取得票据对应的秘密并验证 binder。本模型用 PSK 认证替代证书认证,因此不发送 Certificate 和 CertificateVerify,也不显示证书检查面板。画布从七次传输减为五次,仍包含应用请求、仍省略 EncryptedExtensions。恢复过程不再验证证书与握手签名。
网络往返数仍为一个 RTT,但减少证书认证计算可能缩短实际耗时。不能说“没有任何非对称计算”:这个预设仍生成新的 a,执行临时密钥交换。本次 S 与票据 PSK 都进入密钥派生流程。
这是 PSK + 临时 (EC)DHE 模式。TLS 1.3 也允许 PSK-only 模式,只是没有同样的前向保密性质。本预设中,重放 ClientHello 和 binder 不会让攻击者知道新协商的秘密;后续 PSK 泄露也不足以恢复临时交换保护的应用流量。若只依赖 PSK,后来获得 PSK 的人可能解密相应的记录流量。
边界情况
- TCP 已建连。 TLS 的一个 RTT 不包含 TCP 三次握手。新建远距离连接通常要支付这两段等待。
- 0-RTT 早期数据。 恢复客户端可在第一批消息附带应用数据,但这种数据有重放风险。应用必须判断重复执行是否可接受,并设置重放防护;幂等标签本身不保证请求的所有副作用安全。
- HelloRetryRequest。 客户端没有提供服务端接受的 key share 时,服务端可要求重试,握手增加一个 RTT。双方没有共同参数时则失败。预先提供合适的组可避免重试。
- 客户端证书。 服务端也可要求客户端发送 Certificate 与 CertificateVerify,在内部服务认证中较常见。
- 证书失败。 过期、不受信、名称不符会在本模型的三项检查中失败。误签但链与名称均获接受的证书可能通过,不能保证这三项检查识别所有误签。
- 错误日志。 链、名称、日期问题可表现为
unable to get local issuer certificate、主机名不符或过期。CertificateVerify 签名不符使用decrypt_error,Finished 不符也可使用同一告警;必须结合失败阶段定位,不能只读名字。 - 撤销。 私钥泄露后可撤销证书,但各浏览器的检查机制与失败策略不同。短有效期能缩短风险窗口,不能替代所有撤销检查。
常见错误
- 认为证书直接加密连接。 应用加密密钥来自密钥派生流程。证书公钥用于认证握手,服务端私钥用于签名;本课的加密与身份认证职责不同。
- 认为有效证书证明当前对端身份。 攻击场景中的证书检查全部通过,真正失败的是本次记录上的签名验证。
- 认为安全密钥交换自动保证安全连接。 未认证的 Diffie–Hellman 也能与攻击者协商密钥。
- 认为 TLS 隐藏所有访问线索。 未使用 ECH 的本例暴露 SNI、目标 IP、时间与大小。TLS 保护内容,不隐藏通信发生的事实。
- 认为恢复必然少一个 RTT。 普通恢复减少证书认证工作,网络 RTT 不变;0-RTT 是另一个机制,计算减少仍可能改善总耗时。
- 套用 TLS 1.2 图。 完整 TLS 1.2 通常需要两个 RTT,证书明文发送,还曾支持没有前向保密的 RSA 密钥传输。TLS 1.3 移除了 RSA 密钥传输。在本课的临时交换中,销毁临时秘密后,未来泄露服务端签名私钥不会解开过去会话;PSK-only 与 0-RTT 的性质不同。
模型的简化
p = 23、g = 5 只用于核对 19^6 mod 23 = 2,不具备实际安全性。真实 TLS 1.3 可用 X25519、NIST P-256 等椭圆曲线组,也定义了有限域组。椭圆曲线运算不等于直接在 256 位整数上套用这里的模幂。
本课只用一个密码套件,省略协商,并把 HKDF 简化为少数步骤。真实派生流程包含 early、handshake、application traffic secret,两个方向各自用密钥,还有 exporter 与 resumption secret;派生依赖相应阶段的记录。ServerHello 后必有的 EncryptedExtensions 没有画出。
动画未展开 0-RTT、HelloRetryRequest、客户端证书、证书链构建和撤销,也未使用 ECH。RFC 9849 已定义 ECH,可加密包含私有 SNI 的 ClientHello 内容;它不是本课的明文 SNI 模式。TLS 1.3 规范见 RFC 8446,名称验证规则见 RFC 9525。