3、签名与验签失败:签名数据编码不一致、哈希算法选择错误、椭圆曲线参数不匹配

签名验签失败,是SM2落地时最让人头疼的问题之一。我见过不少团队,算法本身没写错,但就是死活验不过。说白了,问题往往出在「约定」上——你签的时候用一种方式,验的时候用了另一种,那肯定对不上。

这一节,我把最常见的三类原因拆开来讲。你对照着自己的代码,大概率能找到问题。

3.1 签名数据编码不一致

SM2的签名结果是一个(r, s)对。但r和s怎么拼到一起传出去?这里就有讲究了。

常见的编码方式有两种:

编码方式 描述 典型场景
ASN.1 DER 使用TLV结构,将r和s编码为SEQUENCE GM/T 0009标准、X.509证书
原始字节拼接 r和s各占32字节,直接拼接成64字节 部分开源库、国密SDK
⚠️ 常见坑:签名方用ASN.1编码,验签方用原始字节拼接去解析。结果就是r和s的边界对不上,验签必然失败。

我记得有一次帮客户排查问题,他们用Java的Bouncy Castle签名,用C语言的某国密库验签。两边代码看了半天都没问题,最后发现是编码方式不一样。Bouncy Castle默认输出ASN.1,C库只认原始拼接。改了一行解析代码,问题就解决了。

所以我的建议是:在项目初期就定好签名数据的编码格式,并且在接口文档里写清楚。如果对接第三方,先问清楚对方用哪种。

3.2 哈希算法选择错误

SM2签名时,需要对消息先做哈希,再用私钥签名。但哈希算法选错了,也是常见问题。

SM2标准里明确要求使用SM3哈希算法。但有些开发者图省事,用了SHA-256或者MD5。嗯,这里要注意:SM2和SM3是配套设计的,换了哈希算法,签名安全性就没法保证了。

更隐蔽的问题是——哈希结果的长度。SM3输出256位(32字节),SM2的签名算法内部对哈希值有特定的截断和拼接规则。如果你用了SHA-512,输出64字节,SM2签名算法拿到后怎么处理?不同库的实现可能不一样,有的直接截断,有的报错,有的默默用前32字节。这就埋下了隐患。

✅ 正确做法:
  • 签名前:使用SM3对消息做哈希
  • 验签前:同样使用SM3对消息做哈希
  • 确保签名方和验签方使用同一个哈希算法

你可能会问:「那我用SHA-256,两边都用一样的,不也能验过吗?」能验过,但这不是SM2标准。国密合规性检查时,这一条就过不去。我在项目中遇到过客户被监管机构要求整改,就是因为用了SHA-256代替SM3。

3.3 椭圆曲线参数不匹配

SM2使用的是国家密码管理局指定的椭圆曲线。这条曲线的参数是固定的,包括:

  • p:素数域模数
  • a, b:椭圆曲线方程系数
  • G:基点坐标
  • n:基点的阶

如果签名方和验签方使用的曲线参数不一样,那签名验签肯定失败。但更常见的情况是——一方用了SM2标准曲线,另一方用了别的曲线,比如secp256k1(比特币用的那条)。虽然都是256位椭圆曲线,但参数完全不同。

💡 小技巧:调试时,可以把双方使用的曲线参数打印出来对比。重点看G点的x、y坐标和n的值。如果不一样,那就是曲线参数的问题。

还有一种情况:自定义曲线参数。有些老旧系统或者特殊硬件,可能用了非标准的曲线参数。这时候如果和标准SM2互签互验,肯定失败。我的建议是——除非有特殊要求,否则永远使用标准曲线参数。标准参数可以在GM/T 0003-2012里查到。

3.4 核心逻辑流程图

下面这张图,把签名验签的完整流程和三个常见失败点串起来了。你可以对照着检查自己的实现。

SM2签名验签流程与常见失败点 SM2 签名与验签流程 签名方 ① 选择哈希算法(SM3) ② 对消息做SM3哈希 ③ 使用SM2私钥签名 ④ 编码签名数据(r, s) ⑤ 输出签名结果 验签方 ① 解析签名数据(r, s) ② 对消息做SM3哈希 ③ 使用SM2公钥验签 ④ 验签通过? 验签失败,排查原因 传输 常见失败原因 🔴 签名数据编码不一致(ASN.1 vs 原始拼接) 🔴 哈希算法选择错误(SM3 vs SHA-256) 🔴 椭圆曲线参数不匹配(标准曲线 vs 自定义曲线)

3.5 排查思路总结

遇到签名验签失败,别慌。按这个顺序排查:

  1. 先看编码:签名方输出的数据,验签方能不能正确解析?打印出r和s的十六进制,对比一下。
  2. 再看哈希:两边用的哈希算法是不是同一个?哈希结果是不是一样?
  3. 最后看曲线:打印出双方使用的曲线参数,重点看G点和n。
💡 我曾经遇到过一个案例:签名验签在本地测试都通过,上了生产环境就失败。最后发现是生产环境的库版本不同,默认曲线参数被改了。所以,显式指定曲线参数比依赖库默认值更安全。

嗯,这三类问题覆盖了90%以上的签名验签失败场景。你下次遇到类似问题,可以对照着排查一遍,大概率能找到根因。