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 |
我记得有一次帮客户排查问题,他们用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位椭圆曲线,但参数完全不同。
还有一种情况:自定义曲线参数。有些老旧系统或者特殊硬件,可能用了非标准的曲线参数。这时候如果和标准SM2互签互验,肯定失败。我的建议是——除非有特殊要求,否则永远使用标准曲线参数。标准参数可以在GM/T 0003-2012里查到。
3.4 核心逻辑流程图
下面这张图,把签名验签的完整流程和三个常见失败点串起来了。你可以对照着检查自己的实现。
3.5 排查思路总结
遇到签名验签失败,别慌。按这个顺序排查:
- 先看编码:签名方输出的数据,验签方能不能正确解析?打印出r和s的十六进制,对比一下。
- 再看哈希:两边用的哈希算法是不是同一个?哈希结果是不是一样?
- 最后看曲线:打印出双方使用的曲线参数,重点看G点和n。
嗯,这三类问题覆盖了90%以上的签名验签失败场景。你下次遇到类似问题,可以对照着排查一遍,大概率能找到根因。