第三讲:SM2算法原理——椭圆曲线密码体制
聊到SM2,就不能不先说说椭圆曲线密码体制(ECC)。为什么咱们非要从RSA迁移到SM2?说白了,就是ECC能用更短的密钥达到更高的安全强度。我个人做密码学实现这么多年,最直观的感受就是:RSA 2048位的密钥,ECC 256位就能扛住。你想想看,在物联网设备那种资源受限的场景下,这差距有多大。
ECC基础:椭圆曲线是个什么鬼?
椭圆曲线不是椭圆,数学上它满足y² = x³ + ax + b这个方程,再加上一个无穷远点。我刚开始学的时候也犯嘀咕,这玩意儿怎么加密?其实关键在于,椭圆曲线上的点构成一个群,点与点之间可以做加法。这个加法规则挺有意思——过P和Q两点画直线,交曲线于第三点R,然后关于x轴对称一下,就是P+Q的结果。
为什么会这样设计?因为这使得离散对数问题变得极其困难。已知基点G和倍数kG,想反推出k,目前没有任何多项式时间算法能搞定。这就为密码学打下地基了。
核心要点:ECC的安全性建立在椭圆曲线离散对数问题(ECDLP)上。同样安全强度下,ECC的密钥长度远小于RSA。
素域与基点
SM2使用的是素域Fp上的椭圆曲线。数学上叫素域,简单理解就是按p取模的整数集合。p是个大素数,咱们国密标准里指定的那个p是256位的素数。
基点G是定义在曲线上一个公开的点。所有SM2密钥都是基于G算出来的。这里要注意:G的阶n必须是个大素数,不然安全性会打折扣。我记得有一次评审别人的SM2实现,发现G的阶选错了,整个密钥空间直接缩水——这种坑不能踩。
| 参数 | 含义 | 长度(比特) |
|---|---|---|
| p | 素域模数 | 256 |
| a, b | 曲线系数 | 256 |
| G | 基点 | 256+1 |
| n | G的阶 | 256 |
公钥私钥计算
私钥d是个随机数,取值范围是[1, n-2]。公钥P = d * G,就是私钥乘以基点。嗯,就这么简单。但实际实现时要注意:
- 随机数生成必须用真随机数发生器,伪随机数会出大问题。我曾经见过一个系统,私钥生成用的是time(NULL)做种子——这就跟把密码贴在门上差不多。
- 公钥需要压缩或非压缩两种形式。压缩形式只存x坐标加一个标志位,能省一半空间。
实际编码时,私钥的随机数生成建议直接用操作系统的熵池。比如Linux下读/dev/urandom,千万别自己写随机算法。
加密/解密流程
SM2加密用的是混合加密——公钥加密一个对称密钥,真正的数据用对称算法(SM4)加密。具体步骤如下:
- 生成随机数k,计算点C1 = k * G
- 计算共享点S = k * P(对方公钥),从S导出对称密钥
- 用SM4加密明文,得密文C2
- 计算杂凑值C3 = SM3(C2 || 其他参数)
- 输出密文(C1 || C2 || C3)
解密就是反过来:用自己的私钥计算出共享点S,导出对称密钥,再解密C2,最后校验C3。
曾经有团队在解密时忘记校验C3,结果被攻击者篡改密文后,解密出错误数据还浑然不知。记住:C3校验是安全底线,不能跳。
签名/验签流程
SM2签名用的是改进版的ECDSA。跟ECDSA最大的区别是:SM2在签名过程中引入了用户ID,把签名者的身份绑进去了。这个设计在实际项目中有大用处——同一对密钥用在不同的应用场景下,签名不会串。
签名步骤简要概括:
- 计算ZA = SM3(用户ID || 曲线参数 || 公钥)
- 拼接消息M = ZA || 原始消息,算哈希e = SM3(M)
- 生成随机数k,计算点(x1, y1) = k * G
- 计算r = (e + x1) mod n,s = ((1 + d)⁻¹ * (k - r * d)) mod n
- 输出签名(r, s)
验签时会重新计算ZA和e,然后验证r是否等于恢复出来的值。这个步骤里的逆元计算容易出错,我用Python实现的时候踩过坑,整数模逆要有理数求逆的兼容性问题,处理不好签名就验不过。
# Python中正确的模逆实现
def mod_inverse(a, p):
# 扩展欧几里得算法
if a % p == 0:
return 0
lm, hm = 1, 0
low, high = a % p, p
while low > 1:
ratio = high // low
nm, new = hm - lm * ratio, high - low * ratio
lm, low, hm, high = nm, new, lm, low
return lm % p
SM2与ECDSA/ECDH的区别
很多刚接触国密的人会问:SM2不就是ECDSA和ECDH吗?其实不是那么回事。
| 对比维度 | SM2 | ECDSA/ECDH |
|---|---|---|
| 签名引入用户ID | 是,计算ZA绑定身份 | 否,直接对消息哈希签名 |
| 加密方式 | 混合加密(C1||C2||C3) | 原则上只做密钥协商(ECDH) |
| 杂凑算法 | SM3(256位) | SHA系列 |
| 签名公式 | r=(e+x1) mod n | r=x1 mod n |
我个人的经验是,SM2在签名公式里加上e和x1的和而不是直接取x1,这个改动让某些代数攻击手段难以奏效。ECDH只管密钥协商,不处理加密和签名;而SM2把签名、加密、密钥交换三件事用同一套曲线体制统一解决了。这也是国密标准设计上的一个亮点——你只学一套曲线,就能覆盖绝大多数密码应用。
说回迁移实践,理解这些原理只是第一步。实际迁移时你要处理的往往是密钥格式转换、签名数据结构的对接、以及和现有系统的互操作性测试。我建议动手之前先把SM2的这几个流程搞清楚,下次遇到签名验签对不上的问题,你马上就能定位是ZA算错了还是逆元求错了。