一台开发机经 VPN 访问测试网络中的 HTTPS RPC,使用钱包提交一笔无价值交易。出现三个“连接安全”的说法:VPN 已连接,HTTPS 握手成功,交易签名验过。它们保护的对象并不相同。VPN 先解决“这两个网络端点如何商定保护哪些 IP 包、用哪些方向的密钥”,不能因此替钱包签交易,也不保证远端 RPC 给出的区块状态可信。

先把通道建立者与数据保护者分开

IKEv2(RFC 7296)协商 IPsec 安全关联;ESP(RFC 4303)保护落到相应关联上的数据包。一个安全关联,简称 SA,不是一张写着“本 VPN 全部通信都安全”的通行证:它包含标识、算法、密钥、方向、序号等状态;可保护的流量还受流量选择器与路由影响。IKE 自己的 IKE SA 保护后续控制消息;给数据包用的是 Child SA。ESP 双向流量通常使用两条方向不同的 SA。

1
2
3
4
5
6
7
开发机(发起方)                         测试网关(响应方)
IKE_SA_INIT: 提案、临时 DH 值、Ni ----> 选择提案、临时 DH 值、Nr
<---- 双方算 g^ir、SKEYSEED,分出 IKE 控制密钥
IKE_AUTH: 加保护的身份、AUTH、 对上一轮交换做身份认证,
首个 Child SA 提案 <--> 选定首个 Child SA 与流量选择器
后续 CREATE_CHILD_SA: 新 nonce <--> 额外 Child SA / 换钥(可选新 DH)
ESP: 匹配选择器的测试流量 <--> 依据对应方向 SA 对包做保护与序号处理

第一对 IKE_SA_INIT 消息交换临时密钥材料和两个随机 nonce,此时尚不能因为 DH 算出了相同秘密就认定对端身份。第二对 IKE_AUTH 消息将身份和认证方法(如证书签名或合适的共享秘密)绑定到之前交换,才建立面向期望对端的 IKE SA,并创建首个 Child SA。若删除这一步身份绑定,攻击者可分别与两侧做密钥交换,在两侧呈现不同的“双方已协商秘密”,正是 10 中的中间人问题。IKE 有自己的请求/响应 Message ID;别把它当作 ESP 的每包序号。

从共享秘密到两种 SA 的密钥

RFC 7296 §2.14 给出核心关系。用 Ni、Nr 表示两端的 nonce,g^ir 表示本次临时 DH 的共享值,SPIi、SPIr 是 IKE SA 两端各自选的标识。实际字节类型和长度要依协商的 PRF 与变换编码,图中的串接仅标明哪些输入进入派生。

1
2
3
4
5
6
7
8
9
10
SKEYSEED = prf(Ni | Nr, g^ir)
│
└─ prf+(SKEYSEED, Ni | Nr | SPIi | SPIr)
├─ SK_d:派生 Child SA 的 KEYMAT
├─ SK_ai / SK_ar:IKE 控制消息的完整性(分方向)
├─ SK_ei / SK_er:IKE 控制消息的加密(分方向)
└─ SK_pi / SK_pr:参与 AUTH 计算

首个 Child SA: KEYMAT = prf+(SK_d, Ni | Nr)
后续创建并选择新 DH: KEYMAT = prf+(SK_d, g^ir(new) | 新 Ni | 新 Nr)

输出的分段长度由具体协商算法决定。采用组合模式的 PRF/加密/完整性算法配置与 AEAD 配置可能不同;不能据这张图盲目为每个名字截取相同字节数并安装生产 SA。后续创建 Child SA 可以不再做 DH,若需要相对于旧 IKE SA 密钥材料的额外独立前向保密,就要检视新交换是否包含新的 DH。KDF 不创造输入缺少的熵;对固定的 g^ir 和固定 nonces 重算得到同样结果,并不说明系统已获得新随机性。

IKE SA 的 SPI 和 ESP SA 的 SPI 也不是一个字段:前者在 IKE 报文头中是 8 字节,后者在 ESP 包头中为 4 字节。SPI 帮接收者找到对应安全关联及密钥;它不是签名,也不是业务订单号。ESP 的数据包序号服务于每条接收 SA 的防重放窗口。接收端要实际启用对应防重放处理,并配合完整性保护,才可拒绝同一 SA 上被复制的旧包;有序号字段并不自动阻止攻击者发送两次不同但都有效的业务请求。业务层订单唯一 ID 和链上交易 nonce 仍各管各的状态。

一个小实验能证明什么,不能证明什么

examples/cryptography/21_ike_keymat.py 将公开的 RFC 7748 §6.1 X25519 测试密钥交给 cryptography==50.0.2 计算真实原语输出。然后以公开、固定的教学 nonce 与 SPI,用标准库 HMAC-SHA256 充当本例选定的 PRF,按 RFC 7296 的派生表达式计算 SKEYSEED、首段 SK_d 和 Child KEYMAT。不输出它们的值,也不把固定教学 nonce 当作真实协议应使用的随机 nonce。更改 Ni 或 SPIi,SK_d 与原输入不同;更改 Ni,首个 Child KEYMAT 也不同。

脚本另用内存集合记录“1 号包”是否已经出现,作为接收方需保存消费状态的最小教学模型。第一包可接受,完全相同编号的第二次被拒;这个集合没有 SPI 查表、滑动窗口、扩展序号、包的 AEAD 验证,所以不是 ESP 实现或真实防重放协议实验。

先按仓库内 examples/cryptography/README.md 完成环境准备;以下命令从仓库根目录执行。

1
2
python examples/cryptography/21_ike_keymat.py
python -m unittest discover -s examples/cryptography -p 'test_21*.py' -v

这是隔离 venv 的本次路径。2026-10-06 UTC 两条命令退出码为 0、1 项测试通过;公开输出明确 real_ike_or_esp_executed=false。当前环境没有 ip、swanctl 或 strongswan,进程也没有网络管理权限;没有建起 VPN、执行 IKE_AUTH、安装 SA 或抓取 ESP 包。真正验收这些行为需要在授权的隔离网络里装两端组件、冻结版本与配置,验证证书/共享密钥错误身份失败及实际 ESP 重放拒绝;不能把运行派生脚本当成那份证据。

在贯穿场景里,还要观察 VPN 保护到哪一个网关:若它在网关处终止,网关到 HTTPS RPC 的下一段是否受保护取决于后续配置。HTTP 请求如经 HTTPS 再建 TLS,TLS 验的是 RPC 服务身份与通信;交易签名验的是交易授权字节。VPN、HTTPS 或交易验签的任何一项成功,都不意味着交易已执行、进入可信区块或现实订单已交付。

两道带答案的练习

推导题: 画三层:IKE 控制消息、Child SA 上的 ESP 包、HTTPS RPC 上的钱包交易。分别标明双向谁持有密钥、接收方依什么查找密钥。删除 IKE_AUTH 身份认证、删除 ESP 接收方消费序号状态时,分别出现哪一种攻击?

可核对答案: 两端的 IKE SA 具有按方向分开的密钥,控制消息依 IKE SPI 等状态处理;两条 ESP SA 各有单方向密钥,入站包以 SPI(及上下文)匹配 SA;HTTPS 另有 TLS 握手密钥,钱包交易私钥只应留在钱包。没有 IKE_AUTH,主动攻击者可分别 DH 与两边冒充对端;没有实际的 ESP 防重放状态,旧的、仍可验的同 SA 包可被复制到接收侧。ESP 防重放不替代业务订单或交易 nonce。

实验变更题: 将 21_ike_keymat.py 中 INITIATOR_NONCE 的第一字节从 a1 改为 00,再将教学 accept_once 对重复包号的判断删除。分别预测哪两个独立的正例会失败;是否由此测到真实 ESP 的重放处理?

可核对答案: 第一处会改变 SK_d、Child KEYMAT,相对原固定输入的差异断言需要相应改造“对照 nonce”才有意义;若把 INITIATOR_NONCE 本身也改成对照用的值,nonce_changes_sk_d 应失败。第二处导致重复 1 号包也被接受,teaching_replay_model_second_rejects 应失败。两者都是教学模型与原语级变化,真实 IKE/ESP 项仍为 NOT_RUN。

资料与导航