在大屏或电脑上体验更佳——动画和操作区域更宽敞。
输入

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,便于手算。

  1. 初始状态。 TCP 已通,但尚无 TLS 密钥;此前的字节可被路径上的观察者读取。
  2. 客户端生成份额。 私有 a = 6,公开 A = 5^6 mod 23 = 8。私有值旁有锁,不会出现在数据包中。
  3. ClientHello 明文发送。 携带 key_share A = 8 和 SNI 主机名。本例未使用 ECH。真实参数难以由 A 反推 a,小数值不具备这种安全性。
  4. 服务端生成份额。 私有 b = 15,公开 B = 5^15 mod 23 = 19。两个临时私有值为本连接生成,用后丢弃。
  5. 服务端推导 S。 A^b mod 23 = 8^15 mod 23 = 2。它没有接收秘密,却已能派生密钥。
  6. ServerHello。 发送 key_share B = 19。这是本流程最后一个未加密的握手消息,不意味着记录首部也从此不可见。
  7. 客户端推导 S。 B^a mod 23 = 19^6 mod 23 = 2。两边结果相同,S 从未传输。图中阴影标出后续加密的握手内容。
  8. Certificate。 画布以 CN = example.com 简写身份;真实主机名验证使用 subjectAltName,而非 CN。证书携带服务端公钥,链可到达受信 CA。加密传输提供隐私,不改变证书内容本身可公开的性质。
  9. CertificateVerify。 服务端用证书对应私钥签署本次握手记录摘要及规定的上下文。
  10. 服务端 Finished。 用派生的服务端 Finished 密钥计算握手摘要 HMAC。
  11. 三项证书检查。 检查链是否受信、名称是否匹配、是否在有效期内。全部通过,但身份验证尚未结束。
  12. 验证签名。 简化表达式 verify(cert.pub, sig, H(transcript)) 返回 true。证书说明公钥身份,签名证明当前对端持有私钥。仅复制证书会在这里失败。
  13. 验证握手记录。 客户端按自己的记录重新计算 Finished HMAC。匹配说明此前记录在此认证下保持一致。
  14. 客户端 Finished。 返回确认,双方完成握手。
  15. 应用密钥。 HKDF 派生另一组应用流量密钥,HTTP 请求不使用握手流量密钥加密。
  16. 发送 GET / HTTP/1.1。 请求已加密,距离 TCP 建连增加一个 RTT。
  17. 查看结果。 画面计数为七次传输、一次握手签名验证,未发送私钥或共享秘密。七次包含应用请求,并省略 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。