第三讲: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)加密。具体步骤如下:

  1. 生成随机数k,计算点C1 = k * G
  2. 计算共享点S = k * P(对方公钥),从S导出对称密钥
  3. 用SM4加密明文,得密文C2
  4. 计算杂凑值C3 = SM3(C2 || 其他参数)
  5. 输出密文(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算法原理知识结构图 SM2椭圆曲线密码体制 素域Fp与基点G 密钥生成 加密/解密 签名/验签 曲线参数p,a,b 基点G与阶n 点加/倍点运算 私钥 d ∈ [1, n-2] 公钥 P = d * G 密钥对管理 C1 = k * G(随机点) 共享点k*P导出对称密钥 C2加密 + C3杂凑校验 ZA = SM3(用户ID || ...) r = (e + x1) mod n s = (1+d)^-1 * (k - r*d) 对比ECDSA/ECDH:SM2身份绑定 + 混合加密 + 统一曲线 同一套椭圆曲线参数,同时支持签名、加密、密钥协商三种服务

说回迁移实践,理解这些原理只是第一步。实际迁移时你要处理的往往是密钥格式转换、签名数据结构的对接、以及和现有系统的互操作性测试。我建议动手之前先把SM2的这几个流程搞清楚,下次遇到签名验签对不上的问题,你马上就能定位是ZA算错了还是逆元求错了。