上一篇25-2《工具链实操vllm_vqf_sign 的 genkey/sign/verify》下一篇26-1《信任根与验签闭环发布方 → 设备》真机实测通过本文实验已在 RK3588 板端实测完成2026-09方法学与原始记录见仓库 docs 与《实验脚本》目录。一句话导读随机源纪律SM2 签名每次的 k 必须取自内核 CSPRNGk 一旦复用私钥会被纯代数解出原理用笔推演、不演示攻击落点是同一私钥连签三次全不同且各自验签通过的实测。关键词随机源纪律、CSPRNG、k 复用、SM2、国密25-2 里vllm_vqf_sign --sign每签一次都悄悄做了一件事从内核 CSPRNG 取一个新的随机数 k。这篇讲这条纪律为什么是生死线——SM2 签名若复用同一个 k私钥会被纯代数直接解出且不需要任何量子计算机。原理用笔推演不演示攻击纪律用板端实测证明同一私钥同一消息连签三次三次签名完全不同、且各自验签通过。1. 知识点k 为什么必须每次新鲜SM2 签名公式25-1 已给s (1d)^-1 · (k − r·d) mod n假设对同一消息e 相同签了两次且两次用了同一个 k → 两次的椭圆曲线点 [k]G 相同 → r 相同而 s 按上述公式由 (k, r, d) 唯一确定 →两次签名完全一样。r/s 一模一样本身就可疑——这是 k 复用的暴露信号。但真正的灾难是下面这个场景不同消息、同一个 k如用固定随机种子签名不同的模型文件。此时 e 不同 → r₁ ≠ r₂但 k 相同s₁ (k − r₁·d)/(1d) s₂ (k − r₂·d)/(1d)两式相减消去 ks₁ − s₂ (r₂ − r₁)·d/(1d)反解出d (s₁ − s₂) · ((r₂ − r₁) − (s₁ − s₂))⁻¹ mod n。右边全部是公开值 (r₁, s₁, r₂, s₂)——私钥 d 被纯代数解出不需要碰任何加密算法只需要两条同 k 不同消息的签名。历史上有教科书级事故PlayStation 3 的 ECDSA 签名用固定 k2010 年 fail0verflow 团队正是用这条式子解出了索尼的私钥。ECDSA 与 SM2 同族s 公式结构等价k 复用 私钥裸奔。所以纪律只有一条k 必须每次签名从密码学安全随机源现取绝不缓存、绝不复用、绝不手工注入。vllm_crypto.h 88–91 行把这句写成了注释k 复用会泄露私钥。2. 对应代码vc_secure_rand 与签名侧的唯一取数点/* vllm_crypto.c 682–702 */ int vc_secure_rand(uint8_t out[32]) { #ifdef __linux__ while (got 32) { ssize_t r getrandom(out got, 32 - got, 0); /* 686: 内核 CSPRNG */ if (r 0) { ... fopen(/dev/urandom) ... } /* 688–693: 老内核回退 */ got r; } return 0; #else return -1; /* 非 Linux拒绝生成签名侧必须自备 CSPRNG */ #endif }要点① 32 字节分片循环读完getrandom不是一把梭只调一次② Linux 之外显式拒绝返回 -1而不是悄悄用弱随机——宁可不签不签弱签③ 回退路径只有/dev/urandom内核阻塞池同样 CSPRNG没有任何时间种子 / rand()这类伪随机。签名工具侧的调用点唯一/* vllm_vqf_sign.c 233–238每个签名都重新取 k */ uint8_t k[32]; if (tool_secure_rand32(k) ! 0) { ... return 1; } /* 每次 --sign 都走到这里 */ if (vc_sm2_sign(priv, digest, 32, ..., k, r, s) ! 0) ...25-2 的--genkey也走同一tool_secure_rand32工具私钥生成 每次签名随机数共用这一个 CSPRNG 入口——把随机源纪律收敛到单点代码审查只需盯这一个函数。3. 改动后果同一私钥连签三次三次签名全不同实测口径RK3588 / aarch64 / 2026-09-07。harnesssm2_rand_kat.c固定私钥GM/T 附录示例 dA、固定消息nonce discipline test、固定 IDvllm-shs-vqf真实 VQF_SM2_ID连签三次k 每次由vc_secure_rand现取。run0 k5a3eb31627aba27f996571a1636f6941d0e050ec07d60c38753d2e928ca1133e run0 r4298c4a4762ad8353e01f7d502d9b6fa0b09395b4396ba0d89b99ade85a94d65 s075ef6889172f6923b6d07519ffb89102ab91293f6aa8790c77a889dc830f659 verify1 run1 kf13205557a4caeb0edb1f9ee59a14efd71b32f4a778adf821e584ab4acc9bf85 run1 r1b4961960504f9f721291c9d041bbf882882b343a5edbc9faa32203b575877be s86cbbbe10d9fc2ac74a76d42cd13d0182111f6b9e2ec723166ce42d2e6617a87 verify1 run2 k5c7b326044974c6c23eb8266e8784e0b9a12b502ac4fbf2598625cb6c8f67aa4 run2 rb016204a37d3353d166a056b26c9b1ac40a6d1c10112bc46df97343555d1f6a7 sae6d21a7135909d74b214c2341a145d0ea3e919e6c843cd97ab0ec09a0e287f0 verify1 three_signatures_all_distinct1 k_freshness: PASS (CSPRNG per-signature k)推演改动后果对照 25-1 的确定性 harness把sm2_rand_kat.c里的vc_secure_rand(k)换成memcpy(k, 常量, 32)固定 k三次签名会得到逐字节相同的 r/s——这是肉眼可查的 k 复用红线信号。再把同 k 不同消息代入第 1 节的代数任意两对 (r,s) 即可解出 d。也就是说复现本文全部实验用固定 k 没问题KAT 就是这么做的25-1 的 gmssl 对拍也用固定 k但生产签名路径里出现固定/复用 k等同私钥公开。KAT 固定 k 与生产随机 k 的分界就是纪律本身。4. 学员调试任务A 档动手编译运行sm2_rand_kat.c复现三次签名全异、k_freshness: PASS然后把它改成固定 k 重跑观察三次 r/s 完全相同感受复用信号。B 档纯读源码读vllm_crypto.c682–702 与vllm_vqf_sign.c60–85、233–248回答①getrandom返回可能不足 32 字节被信号打断代码为什么必须循环若只调一次不检查返回值会怎样② Windows 上tool_secure_rand32回退到 BCryptGenRandom为什么不能回退到rand()/time()③ 用纸笔完成第 1 节代数给定 (r₁,s₁)、(r₂,s₂) 且 k 相同写出解出 d 的完整推导不要跳步。预期输出三次签名全异 固定 k 复现全同的对照日志以及一张手写/打字的 k 复用推导能讲清每条签名都要新 k而不只是背结论。收尾本篇源码点名vllm_crypto.cvc_secure_rand682–702、vllm_vqf_sign.c60–85 随机数包装、233–238 每签取 k、vllm_crypto.h88–91 k 复用警告注释开源仓库Kestrel-LLM (Gitee)AGPL-3.0-or-later 或商业许可二选一下篇预告工具会用了、k 纪律立了——但签名文件拿到设备上引擎凭什么信任Day 26 进供应链签名接入信任根从哪来、VQF_FLAG_SIGNED文件加载时走哪条验签路径、以及亲手做一次掉包攻击看它怎么被拒。