SM2算法原理:椭圆曲线密码体制、密钥生成、签名与验签、加密与解密

聊到国密改造,绕不开的核心就是SM2算法。我个人觉得,理解SM2不能光看数学公式,得把它当成一套“如何在金融交易中安全发消息”的完整方案来看。

SM2基于椭圆曲线密码体制,也就是ECC。你想想看,传统的RSA靠大整数分解,密钥越长越安全。而椭圆曲线靠的是“椭圆曲线上的离散对数问题”,在同样的安全强度下,密钥可以短很多。说白了,就是更省资源、更快。

核心概念: SM2算法是一套完整的公钥密码体系,涵盖密钥生成、数字签名、密钥交换和公钥加密。它使用特定的256位椭圆曲线参数,由中国国家密码管理局认证。

椭圆曲线密码体制:从数学到工程

数学上,SM2定义在素域Fp上。公式是 y² = x³ + ax + b。嗯,这里要注意,国家规定了具体的参数a、b、p。这些参数是固定的,我们在SDK里不用自己算,直接用就行。

说白了,椭圆曲线就是一个点集合。你在曲线上取一个基点G,私钥是一个大整数d,公钥就是d*G。这就好比你在操场上从起点走d步,你到了终点,别人看到终点却很难反推出你走了多少步。

我在项目中遇到过一个问题:有人尝试自己实现参数验证,结果p参数长度少了一个字节,导致整条曲线都不合法。搞得我排查了半天,最后还是用标准库重新初始化才搞定。个人习惯,直接用SM2的标准曲线参数,不要自己手写。

参数 说明 典型值(SM2标准)
p 素域模数 FFFFFFFE FFFFFFFF ...
a 椭圆曲线参数 FFFFFFFE FFFFFFFF ...
b 椭圆曲线参数 28E9FA9E 9D9F5E34 ...
G 基点坐标(x,y) 固定值
n 基点G的阶 FFFFFFFF ...

SM2密钥生成:安全的起始点

密钥生成的流程不复杂,但这里的“随机数”是命门。

  1. 生成随机数d:系统生成一个1到n-1之间的随机整数。这就是私钥。
  2. 计算公钥P:P = d * G,也就是私钥乘以基点,得到另一个椭圆曲线上的点。
  3. 输出:私钥d(必须保密),公钥P(公开分发)。
避坑指南: 我建议你一定要使用硬件真随机数发生器,或者经过国密认证的随机数生成模块。我曾经见过为了测试优雅直接new Random()来生成私钥,结果私钥可预测,整个系统形同虚设。金融场景下,随机性就是安全性。

SM2的公钥有两种格式:压缩格式和非压缩格式。压缩格式只存储点的x坐标,再加上一个奇偶标识。非压缩格式完整保存x和y坐标。我个人在实际对接中,绝大多数情况下用非压缩格式,兼容性最好。

SM2签名与验签:不可否认性的核心

签名是什么?就是证明这消息是我发的,而且事后不能抵赖。在金融行业,比如一笔转账指令,一定要用SM2签名,才能保证交易的有效性。

SM2签名算法基于椭圆曲线,签名结果由两个大整数(r, s)组成。验签时,对方用公钥和原始消息,验证这个(r, s)是否有效。

你想想看,为什么SM2的签名比其他方案更优?因为它结合了椭圆曲线的高安全性和认证算法的高效率。它每一步都是国密标准严格定义的,不存在什么“可选的步骤”。

// SM2签名流程伪代码(简化)
// 输入:私钥d,消息M
// 输出:签名值(r, s)

1. 计算摘要 e = SM3(M)   // SM3哈希
2. 生成随机数 k
3. 计算点 (x1, y1) = k * G
4. 计算 r = (e + x1) mod n
5. 计算 s = ((1 + d)^(-1) * (k - r * d)) mod n
6. 输出 (r, s)
注意: 验证签名时,如果r或s为0,必须直接返回失败。我曾经调试过一个案例,因为签名结果中的s刚好等于0,但某SDK没有处理这个边界情况,导致错误地通过了验签。这是不能接受的。

SM2加密与解密流程:端到端的数据保护

加密和解密与签名验签是两套不同的流程。加密时用接收方的公钥,解密时用接收方的私钥。说白了,只有持有私钥的人才能解开。

SM2的加密流程是混合加密:先用SM2算法协商出一个共享密钥,然后用对称加密算法(通常是SM4)来加密实际数据。这样既发挥了非对称密钥管理方便的优势,又利用对称加密的高效率。

加密流程:

  • 接收方公钥P_B:用公钥加密数据。
  • 生成临时密钥对 (k, C1):C1 = k * G。
  • 计算共享点 S = k * P_B。
  • 使用KDF(密钥派生函数)从S中导出对称密钥t。
  • 用t加密明文,得到密文C2。
  • 计算MAC消息认证码C3,保证完整性。
  • 输出:C = C1 || C2 || C3。

解密流程:

  • 接收方私钥d_B:用私钥解密。
  • 从C中提取C1、C2、C3。
  • 计算共享点 S = d_B * C1。
  • 用同样的KDF派生对称密钥t。
  • 用C3验证C2的完整性。
  • 用t解密密文C2,得到明文。
原则: 加密时,临时私钥k必须每次都重新生成,不能复用。我检查过多个银行的改造代码,有些为了方便会缓存临时密钥,这是一个严重的安全漏洞。
SM2算法知识体系 密钥生成 生成随机数 d 计算 P = d * G 输出 (d, P) 签名 & 验签 签名:输入 d + M 计算 (r, s) 验签:输入 P + M 验证 (r, s) 加密 & 解密 加密:输入 P_B + 明文 生成 C1, C2, C3 解密:输入 d_B + 密文 验证 C3,恢复明文 SM2椭圆曲线基础 曲线方程:y² = x³ + ax + b 基点G,阶n,素域p 安全性基于椭圆曲线离散对数难题

实际对接中的常见问题

我和多家银行合作过,发现SM2接入改造做得好的和做得烂的,差距就在细节上。下面几个点,是我个人认为最容易踩坑的地方:

问题点 现象 解决方案
密钥格式不统一 一方用PEM,一方用DER,解析失败 统一使用DER编码或X.509证书标准
签名数据编码问题 签名值r/s长度不定,导致验签失败 严格执行固定长度输出,补齐前导零
加密密文拼接顺序 C1 || C2 || C3 顺序搞错 严格按照国密标准顺序
随机数重用 两次签名用的随机数一样 每次签名必须重新生成真随机数
我建议: 真的,别自己在业务代码里拼凑SM2的加解密流程。直接用成熟的国密SDK,比如已经过认证的硬件密码模块或经过大量验证的开源实现。自己实现一遍,出错概率太高了。

好了,以上就是SM2算法的核心原理。从椭圆曲线基础,到密钥生成、签名验签、加解密流程,每个环节都和你系统的最终安全性挂钩。理解了这些,后面我们正式进入SDK接入改造时,你就会清楚每一步要做什么了。