2. TLS 1.3握手与密钥调度回顾
TLS 1.3(RFC 8446)把握手压缩到一次往返(1-RTT),并把密钥派生从"握手协商算法、记录层再推导"改成了"握手过程中同步推进的密钥调度(Key Schedule)"。理解这条主线,是判断PQC能插在哪里的前提:PQC不是替换某个算法名字那么简单,它改变的是ClientHello里携带的密钥共享、ServerHello里选择的组、以及共享秘密进入HKDF-Extract的位置。
一、1-RTT握手的消息序列
标准1-RTT握手(无PSK、无客户端证书)的消息顺序如下:
- ClientHello:客户端发送 random、legacy_session_id、cipher_suites、
supported_groups、signature_algorithms、key_share、supported_versions等扩展。其中key_share携带客户端为若干命名组生成的临时公钥(如X25519的32字节公钥)。 - ServerHello:服务端选定一个组,返回自己的
key_share,并给出supported_versions = TLS 1.3。此时双方各自用ECDH计算出共享秘密Z。 - {EncryptedExtensions}、{Certificate}、{CertificateVerify}、{Finished}:从ServerHello之后的所有握手消息都被握手密钥加密,花括号表示受保护。
- {Finished}:客户端发送自己的Finished,握手完成,随后切换到应用流量密钥。
关键点在于:ServerHello之后的消息已经加密。这意味着任何"在握手后期才协商的PQC算法"都无法保护ServerHello本身,也无法参与早期握手密钥的派生。PQC的插入点必须落在ClientHello/ServerHello这一对明文消息所能承载的扩展与密钥交换数据里。
二、密钥调度:共享秘密如何变成各类密钥
TLS 1.3的密钥调度以HKDF为核心,分两个阶段:
- HKDF-Extract:把输入密钥材料(IKM)与盐(salt)压缩成固定长度的伪随机密钥(PRK)。第一次Extract的salt是0,IKM是
PSK或0;第二次Extract的salt是上一阶段的Derive-Secret(., "derived", ""),IKM是(EC)DHE共享秘密。 - HKDF-Expand-Label:从PRK派生出具体用途的密钥,标签包括
"c hs traffic"、"s hs traffic"、"c ap traffic"、"s ap traffic"、"exp master"、"res master"等。
简化后的派生链为:
0
| HKDF-Extract(salt=0, IKM=PSK 或 0)
v
Early Secret
| Derive-Secret(., "derived", "")
v
Derive-Secret 输出
| HKDF-Extract(salt=上一步, IKM=(EC)DHE)
v
Handshake Secret
| Derive-Secret(., "c hs traffic", ClientHello...ServerHello)
| Derive-Secret(., "s hs traffic", ClientHello...ServerHello)
v
Master Secret
| Derive-Secret(., "c ap traffic", ClientHello...server Finished)
| Derive-Secret(., "s ap traffic", ClientHello...server Finished)
v
应用流量密钥
注意Derive-Secret的第二个参数是握手上下文哈希(Transcript-Hash),即到当前消息为止所有握手消息的哈希。这意味着:任何进入握手的PQC公钥、密文或扩展,只要出现在ClientHello/ServerHello中,就会被纳入Transcript-Hash,从而影响后续所有密钥。这是PQC与TLS 1.3集成的安全基础——攻击者无法在不被发现的情况下篡改PQC密钥交换数据。
三、可插入PQC的扩展与密钥交换环节
结合上面的消息序列与密钥调度,PQC的插入点可以归纳为四类:
| 插入点 | 位置 | 作用 | 典型方案 |
|---|---|---|---|
| 混合密钥交换 | ClientHello/ServerHello的key_share | 把PQC KEM公钥/密文与ECDH共享秘密一起作为IKM | X25519MLKEM768、SecP256r1MLKEM768 |
| PQC签名认证 | Certificate/CertificateVerify | 用ML-DSA/SLH-DSA替换或混合RSA/ECDSA签名 | ML-DSA-65、SLH-DSA-SHA2-128s |
| 额外密钥共享扩展 | ClientHello的key_share多组 | 客户端预发多个组的公钥,服务端选一个 | X25519 + MLKEM768 并列 |
| PSK/会话恢复 | NewSessionTicket / pre_shared_key | PQC认证的会话票据,恢复时免去证书验证 | ML-DSA签名的ticket |
其中混合密钥交换是最优先、影响面最小的插入点。它的做法是:客户端在key_share中同时发送X25519公钥和ML-KEM-768公钥(或封装后的密文),服务端返回自己的X25519公钥和ML-KEM密文;双方分别计算Z_ecdh和Z_kem,再拼接成Z = Z_ecdh || Z_kem作为HKDF-Extract的IKM。这样即使ML-KEM被未来量子计算机攻破,X25519仍然提供经典安全性;反之亦然。
ECDH || KEM或按组名排序。顺序不一致会导致双方派生出不同的Handshake Secret,握手在Finished校验时失败。四、用一张图看清插入位置
下面的SVG把1-RTT握手的消息流、密钥调度阶段和PQC插入点放在同一张图上。左侧是消息序列,中间是密钥调度链,右侧标注可插入PQC的环节。可以看到:PQC密钥交换数据进入key_share后,直接参与Handshake Secret的Extract;PQC签名则影响CertificateVerify,但不改变密钥调度结构。
五、为什么PQC不能"晚插"
从图中可以清楚看到,Handshake Secret的Extract发生在ServerHello之后、EncryptedExtensions之前。如果PQC密钥交换数据放在EncryptedExtensions或更晚的消息里,它就无法参与Handshake Secret的派生,也就无法保护后续的Certificate和Finished。更严重的是,攻击者可以在ServerHello到EncryptedExtensions之间篡改PQC数据而不被Transcript-Hash发现——因为那段数据还没进入哈希。因此,PQC密钥交换必须放在ClientHello/ServerHello的key_share中,PQC签名必须放在Certificate/CertificateVerify中,这是TLS 1.3协议结构决定的硬约束。
六、一个可观察的握手参数示例
假设客户端配置X25519MLKEM768混合组,抓包可以看到:
ClientHello
supported_groups: x25519_mlkem768 (0x11ec), x25519 (0x001d)
key_share:
group: x25519_mlkem768
key_exchange: 1216 字节(32字节X25519公钥 + 1184字节ML-KEM-768公钥)
ServerHello
key_share:
group: x25519_mlkem768
key_exchange: 1120 字节(32字节X25519公钥 + 1088字节ML-KEM-768密文)
双方计算Z = Z_x25519 || Z_mlkem(32 + 32 = 64字节),送入HKDF-Extract得到Handshake Secret。如果服务端不支持该混合组,会回退到x25519,此时Z只有32字节,握手仍然成功但失去PQC保护。因此部署后必须通过抓包或openssl s_client -groups X25519MLKEM768确认实际协商的组,而不是只看配置。