4. SM2椭圆曲线公钥密码算法

SM2算法,说白了就是咱们国家自己的ECC(椭圆曲线密码学)。我在做国密改造项目时,第一个碰到的硬骨头就是它。很多人觉得椭圆曲线很玄乎,其实没那么复杂。你把它想象成一个在椭圆曲线上做点运算的游戏,就对了。

4.1 椭圆曲线基础

椭圆曲线不是椭圆,而是一个方程:y² = x³ + ax + b。SM2用的是特定的参数,我直接给你看核心参数:

参数 值 说明
p FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF 素数域模数(256位)
a FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC 曲线系数
b 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93 曲线系数
G (x,y) 坐标对 基点(生成元)
n FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123 基点阶(素数)

嗯,这里要注意:SM2的曲线参数是固定的,你不能自己随便改。我曾经见过一个项目,有人想「优化」性能,自己换了曲线参数——结果验签死活过不去。后来查了标准才发现,国标里写死了这些值。

核心概念:椭圆曲线上的点构成一个加法群。两个点相加得到第三个点,这个「加法」不是坐标相加,而是通过几何规则(弦切法)计算。私钥就是一个大整数d,公钥就是Q = d * G(G点自加d次)。

4.2 SM2密钥对生成

生成密钥对其实就三步,我习惯这么记:

  1. 选私钥:在[1, n-2]范围内随机选一个大整数d
  2. 算公钥:计算Q = d * G(椭圆曲线标量乘法)
  3. 输出:私钥d(256位),公钥Q(x,y坐标,各256位)

代码实现大概长这样:

// SM2密钥对生成(伪代码)
function generateSM2KeyPair() {
    // 1. 生成随机数作为私钥
    let d = randomBigInt(1, n - 2);
    
    // 2. 计算公钥 Q = d * G
    let Q = pointMultiply(d, G);
    
    // 3. 返回密钥对
    return {
        privateKey: d,      // 256位大整数
        publicKey: Q        // 椭圆曲线上的点 (x, y)
    };
}

我的经验:生成随机数时一定要用密码学安全的随机数生成器(CSPRNG)。我见过有人用Math.random()生成私钥——结果被暴力破解了。别笑,真事。

4.3 SM2签名与验签

SM2签名算法基于椭圆曲线离散对数难题。说白了就是:给你公钥Q和签名(r,s),你没法反推出私钥d。

签名流程:

  1. 计算消息摘要e = SM3(Z || M),其中Z是用户身份标识
  2. 生成随机数k ∈ [1, n-1]
  3. 计算椭圆曲线点(x1, y1) = k * G
  4. 计算r = (e + x1) mod n,如果r=0或r+k=n则重来
  5. 计算s = ((1 + d)⁻¹ * (k - r * d)) mod n,如果s=0则重来
  6. 输出签名(r, s)

验签流程:

  1. 检查r, s是否在[1, n-1]范围内
  2. 计算e = SM3(Z || M)
  3. 计算t = (r + s) mod n,如果t=0则验签失败
  4. 计算椭圆曲线点(x1, y1) = s * G + t * Q
  5. 计算R = (e + x1) mod n,如果R == r则验签通过

避坑指南:我曾经在项目中遇到一个坑——签名时每次都要生成新的随机数k。如果两次签名用了相同的k,私钥就会被算出来。索尼PS3当年就是这么翻车的。所以一定要确保k的随机性。

4.4 SM2加密与解密

SM2加密用的是混合加密:椭圆曲线密钥交换 + 对称加密。说白了就是先用椭圆曲线算出一个共享密钥,再用这个密钥加密实际数据。

加密流程:

  1. 生成随机数k ∈ [1, n-1]
  2. 计算椭圆曲线点C1 = k * G(这是密文的一部分)
  3. 计算共享点S = k * Q(Q是接收方公钥)
  4. 从S的x坐标导出对称密钥t = KDF(x2 || y2, klen)
  5. 用t加密明文:C2 = M ⊕ t(异或加密)
  6. 计算校验值C3 = SM3(x2 || M || y2)
  7. 输出密文C = C1 || C2 || C3

解密流程:

  1. 从C中提取C1,检查C1是否在曲线上
  2. 计算共享点S = d * C1(d是接收方私钥)
  3. 从S的x坐标导出对称密钥t = KDF(x2 || y2, klen)
  4. 解密:M = C2 ⊕ t
  5. 校验:计算SM3(x2 || M || y2)是否等于C3

关键点:SM2加密不是直接对数据做椭圆曲线运算,而是用椭圆曲线协商出一个对称密钥,再用对称加密处理数据。这样做的好处是效率高——椭圆曲线运算只做一次,大数据量加密用对称算法。

我做个对比,帮你理解SM2和RSA的区别:

特性 SM2 RSA
密钥长度 256位 2048位
签名速度 快 慢
加密速度 快(混合加密) 慢(直接加密)
安全性 256位 ≈ RSA 3072位 2048位
算法复杂度 中等 简单

你看,SM2用256位的密钥就能达到RSA 3072位的安全强度。这就是椭圆曲线的优势——密钥短、速度快、安全性高。

SM2算法核心流程 密钥生成 私钥d ∈ [1, n-2] 数字签名 SM3摘要 + 椭圆曲线运算 加密 混合加密(KDF + 异或) 验签 s*G + t*Q 验证 解密 d*C1 恢复共享密钥 核心依赖 椭圆曲线离散对数难题(ECDLP) SM3密码杂凑算法 + 密钥派生函数KDF

这张图把SM2的四个核心功能串起来了。密钥生成是基础,签名/验签和加密/解密都依赖它。你注意看,签名和加密用的是不同的运算路径——签名用私钥,验签用公钥;加密用公钥,解密用私钥。这个「非对称」的特性,正是公钥密码的精髓。

我的建议:刚开始接触SM2时,别急着看数学证明。先跑通一个Hello World级别的签名验签程序,感受一下流程。等用熟了,再回头啃椭圆曲线理论,会轻松很多。