3. 签名生成审计:随机数k的生成质量、杂凑值计算流程、签名参数合法性

签名生成这个环节,是SM2安全审计里最容易出问题的地方之一。我这些年做安全审计,见过太多「算法实现没问题,但签名就是能被伪造」的案例。说白了,问题往往不出在算法本身,而出在实现细节上。

今天咱们就聚焦三个核心点:随机数k的生成质量、杂凑值计算流程、签名参数合法性。这三个点任何一个出问题,整个签名体系就形同虚设。

核心观点:SM2签名的安全性,本质上依赖于随机数k的不可预测性。k一旦泄露或可预测,私钥就等于公开了。

3.1 随机数k的生成质量审计

先说说随机数k。SM2签名算法里,k是每次签名时临时生成的随机数,范围在[1, n-1]之间。n是SM2椭圆曲线的阶,一个256位的大素数。

为什么k这么重要?因为签名值(r, s)中的r = (k * G).x,s = (1 + dA)^(-1) * (k - r * dA) mod n。如果你能猜出k,或者两次签名用了同一个k,私钥dA就能被直接算出来。

我记得有一次审计某个金融系统的SM2签名模块,发现他们的随机数生成器用的是C语言的rand()函数。嗯,你没看错,就是那个伪随机数生成器。我当时就跟他们说:「你们这个签名,跟直接把私钥写在代码里没什么区别。」

审计要点清单

审计项 检查内容 常见问题
随机源 是否使用密码学安全的随机数生成器(CSPRNG) 使用rand()、random()等非安全随机源
熵源质量 是否从操作系统熵池(如/dev/urandom)获取种子 固定种子、时间戳作为种子
k的唯一性 每次签名是否生成新的k,且不重复 k重用导致私钥泄露
k的比特长度 k的比特长度是否等于n的比特长度(256位) k比特长度不足,降低安全性
k的偏置检测 k的分布是否均匀,无统计偏置 高位或低位存在偏置

⚠️ 我曾经遇到过一个案例:某厂商的SM2签名实现,为了「性能优化」,把k的生成改成了「预先生成一批k,存起来,签名时取用」。结果这批k是用同一个种子生成的,而且没有做重复检测。审计时发现,1000个签名里,有3个用了相同的k。私钥直接暴露。

排查方法

  1. 源码审查:检查随机数生成器的调用链。确认是否调用了操作系统提供的安全随机接口(如Linux的getrandom()、Windows的BCryptGenRandom())。
  2. 日志审计:如果系统有签名日志,检查是否有相同的r值出现。r相同意味着k相同,这是最直接的证据。
  3. 统计测试:收集大量签名中的r值,做卡方检验或KS检验,看分布是否均匀。我习惯用NIST SP 800-22的随机性测试套件来跑一遍。
  4. 代码插桩:在测试环境里,对k的生成过程插桩,打印k的十六进制值,人工检查是否有规律。

💡 个人习惯:我审计时,会要求厂商提供「签名过程中k的生成流程图」。如果对方拿不出来,或者流程图里没有标注随机源,我会直接标记为高风险。

3.2 杂凑值计算流程审计

SM2签名不是直接对消息签名,而是对消息的杂凑值签名。这个杂凑值计算流程,比你想的要复杂一些。

SM2标准里,杂凑值计算分两步:

  1. 计算ZA值:ZA = H256(ENTLA || IDA || a || b || xG || yG || xA || yA)
  2. 计算e值:e = H256(ZA || M)

其中,ENTLA是用户IDA的比特长度,IDA是用户标识,a、b是椭圆曲线参数,xG、yG是基点坐标,xA、yA是用户公钥坐标,M是原始消息。

你想想看,这个ZA值里包含了公钥信息。这意味着什么?意味着同一个消息,不同用户签名时,杂凑值是不同的。这是SM2区别于ECDSA的一个重要设计。

审计要点清单

审计项 检查内容 常见问题
ZA计算完整性 ZA是否包含了所有必需字段 遗漏IDA或公钥坐标
字节序处理 ZA和e计算中的字节序是否与标准一致 大端小端混淆
哈希算法 是否使用SM3(国密哈希算法) 误用SHA-256或其他算法
消息预处理 消息M是否原样输入,无额外填充 对消息做了不必要的编码转换
IDA默认值 IDA是否使用标准默认值(1234567812345678) IDA为空或长度错误

避坑指南:我曾经审计过一个项目,他们的ZA计算里把IDA写死了空字符串。结果就是,所有用户的ZA值都一样,签名失去了用户关联性。更严重的是,攻击者可以用自己的签名冒充其他用户——因为杂凑值计算里没有区分用户身份。

排查方法

  1. 向量测试:使用SM2标准附录中的测试向量,逐字节比对ZA和e的计算结果。这是最可靠的方法。
  2. 代码走读:检查ZA计算函数的输入参数列表,确认是否传入了IDA、公钥等必要信息。
  3. 日志对比:在测试环境中,打印ZA和e的中间值,与标准实现(如GmSSL)的输出做对比。

3.3 签名参数合法性审计

签名生成完成后,输出的(r, s)对需要满足一系列合法性条件。这些条件不是可有可无的,它们是安全性的最后一道防线。

SM2标准规定,签名(r, s)必须满足:

  • r ∈ [1, n-1]
  • s ∈ [1, n-1]
  • r + s ≠ n

为什么要有这些限制?我简单解释一下:

  • 如果r=0或s=0,签名验证时会出现除零错误,或者直接绕过验证逻辑。
  • 如果r+s=n,某些实现中会导致验证公式出现特殊情况,可能被利用。

⚠️ 注意:有些实现为了「性能」,跳过了这些合法性检查。我见过一个嵌入式设备上的SM2实现,s=0时直接返回「验证通过」。攻击者只需要构造一个s=0的签名,就能通过任何验证。这简直是在给攻击者开后门。

审计要点清单

审计项 检查内容 常见问题
r值范围 r是否在[1, n-1]区间内 r=0或r≥n
s值范围 s是否在[1, n-1]区间内 s=0或s≥n
r+s条件 r+s是否等于n r+s=n时未做特殊处理
签名输出格式 签名是否按标准DER编码或固定长度格式输出 格式错误导致解析失败

排查方法

  1. 边界测试:构造r=0、r=n、s=0、s=n、r+s=n等边界情况,看签名生成函数是否会正确处理。
  2. 代码审查:检查签名生成函数的末尾,是否有对(r, s)的合法性判断逻辑。
  3. 模糊测试:使用随机输入,看签名生成是否会崩溃或输出非法值。

💡 我建议:在代码里加一个断言(assert),在签名生成后立即检查(r, s)的合法性。这样在测试阶段就能发现问题,而不是等到上线后被攻击者利用。

3.4 知识体系总览

下面这张图,把签名生成审计的三个核心维度串起来了。你可以把它当作审计时的检查清单。

SM2签名生成审计知识体系 签名生成审计 随机数k生成质量 杂凑值计算流程 签名参数合法性 随机源检查 唯一性检测 比特长度 统计偏置 ZA完整性 字节序 哈希算法 IDA默认值 r值范围 s值范围 r+s条件 输出格式 审计核心原则:随机数不可预测 + 杂凑值完整 + 参数边界合法 三者缺一不可,任何一个环节出问题,签名体系就形同虚设

好了,以上就是签名生成审计的三个核心维度。你审计时,按这个框架去查,基本不会漏掉关键点。记住:随机数k是命门,杂凑值计算是基础,参数合法性是底线。三条线都守住,签名安全才有保障。