2. SM2 非对称算法原理

SM2 国密算法,说白了就是咱们国家自己的椭圆曲线公钥密码体系。我在做金融系统迁移时接触最多就是它——从 RSA 切到 SM2,不只是换套 API,底层数学逻辑完全不同。这一章我把核心原理拆开讲,你理解透了,后面适配迁移才不会踩坑。

2.1 SM2 椭圆曲线参数

SM2 用的是一条特定的椭圆曲线,参数由国家密码管理局定义。我个人习惯把这组参数记成「一套固定的数学环境」——曲线定了,所有运算都在这个框架里跑。

参数 含义 值(十六进制)
p 素数模数,定义有限域 FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF
a 曲线方程 y² = x³ + ax + b 中的系数 FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC
b 曲线方程中的常数项 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
Gx 基点 G 的 x 坐标 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7
Gy 基点 G 的 y 坐标 BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0
n 基点 G 的阶(素数) FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
我的经验: 参数 n 是椭圆曲线上的点的个数(素数阶)。密钥对生成时,私钥必须小于 n。我曾经见过有人直接把随机数当私钥,结果超出 n 导致验签失败——这种低级错误排查起来特别费时间。

2.2 密钥对生成流程

SM2 密钥对生成,本质上就是「选一个随机数当私钥,用椭圆曲线乘法算出公钥」。流程不复杂,但随机数质量直接决定安全性。

  1. 生成随机数 d:d ∈ [1, n-2],使用密码学安全的随机数发生器。
  2. 计算公钥 P:P = d × G,即私钥 d 乘以基点 G,得到椭圆曲线上的另一个点。
  3. 输出密钥对:私钥为 d(256 位整数),公钥为 P(包含 x, y 坐标,共 512 位)。

核心公式: 公钥 = 私钥 × 基点。这个乘法是椭圆曲线上的标量乘法,不是普通整数乘法。你想想看,从公钥反推私钥,就是椭圆曲线离散对数问题——目前没有多项式时间解法,这就是安全性的根基。

// 伪代码示例:SM2 密钥对生成
function generateSM2KeyPair():
    d = secureRandom(1, n-2)          // 私钥
    P = scalarMultiply(d, G)          // 公钥
    return (d, P)
避坑指南: 我曾经在嵌入式设备上直接用系统 rand() 生成私钥,结果随机数周期太短,两个设备生成了相同的私钥——这在密码学里叫「密钥碰撞」,后果是灾难性的。务必使用硬件随机数或操作系统提供的 CSPRNG。

2.3 数字签名与验签机制

SM2 数字签名用的是 SM2 签名算法(基于椭圆曲线),不是 ECDSA。两者流程相似,但细节不同。我当年做互操作性测试时,就被这个「相似但不兼容」坑过。

签名流程

  1. 计算消息摘要 e = SM3(Z || M),其中 Z 是用户标识和公钥的哈希,M 是原始消息。
  2. 生成随机数 k ∈ [1, n-1]。
  3. 计算椭圆曲线点 (x₁, y₁) = k × G。
  4. 计算 r = (e + x₁) mod n。如果 r = 0 或 r + k = n,回到步骤 2。
  5. 计算 s = ((1 + d)⁻¹ × (k - r × d)) mod n。如果 s = 0,回到步骤 2。
  6. 输出签名 (r, s)。

验签流程

  1. 检查 r, s ∈ [1, n-1],不通过则验签失败。
  2. 计算消息摘要 e' = SM3(Z || M)。
  3. 计算 t = (r + s) mod n,如果 t = 0 则验签失败。
  4. 计算椭圆曲线点 (x₁', y₁') = s × G + t × P。
  5. 计算 R = (e' + x₁') mod n,如果 R = r 则验签通过,否则失败。
我的习惯: 验签时一定要先检查 r 和 s 的范围。有些实现省略了这步,攻击者可以伪造签名通过边界检查——我在一次安全审计中亲眼见过这种漏洞。

2.4 公钥加密与解密原理

SM2 公钥加密用的是混合加密:椭圆曲线密钥交换 + 对称加密。说白了,就是用 SM2 协商一个临时密钥,再用 SM4 加密实际数据。这样做既利用了非对称加密的密钥分发优势,又保留对称加密的高效。

加密流程

  1. 生成随机数 k ∈ [1, n-1]。
  2. 计算临时公钥 C₁ = k × G。
  3. 计算共享点 S = k × P(P 是接收方公钥)。
  4. 从 S 的 x, y 坐标派生对称密钥 K = KDF(S_x || S_y, 密钥长度)。
  5. 用 K 加密明文 M,得到密文 C₂。
  6. 计算哈希 C₃ = SM3(S_x || M || S_y)。
  7. 输出密文 C = C₁ || C₂ || C₃。

解密流程

  1. 从 C 中提取 C₁,检查 C₁ 是否在曲线上。
  2. 计算共享点 S' = d × C₁(d 是接收方私钥)。
  3. 从 S' 派生对称密钥 K' = KDF(S'_x || S'_y, 密钥长度)。
  4. 用 K' 解密 C₂,得到明文 M'。
  5. 计算哈希 C₃' = SM3(S'_x || M' || S'_y),与 C₃ 比对,一致则解密成功。
注意: 解密时一定要验证 C₁ 是否在椭圆曲线上。我曾经遇到过一种攻击:攻击者把 C₁ 替换成不在曲线上的点,导致派生密钥可控——这就是「无效曲线攻击」。SM2 标准里明确要求做这个检查,但有些开源实现偷懒了。

2.5 核心逻辑关系图

下面这张图把 SM2 的密钥生成、签名验签、加解密串在一起,你看完应该能理解整个体系是怎么运转的。

SM2 核心逻辑关系图 密钥对生成 私钥 d ∈ [1, n-2] 公钥 P = d × G 数字签名 e = SM3(Z || M) 输出 (r, s) 验签 R = (e' + x₁') mod n R == r ? 通过 : 失败 公钥加密 C₁ = k × G, K = KDF(...) 输出 C₁ || C₂ || C₃ 私钥解密 S' = d × C₁, K' = KDF(...) 验证 C₃' == C₃ 共享椭圆曲线参数 (p, a, b, G, n) 所有运算基于同一曲线,确保互操作性 说明:私钥 d 是核心秘密,公钥 P 公开;签名用私钥,验签用公钥;加密用公钥,解密用私钥。

2.6 关键对比:SM2 vs ECDSA vs RSA

做迁移时,你肯定要跟团队解释为什么换 SM2。我整理了一张对比表,开会时直接贴出来就行。

特性 SM2 ECDSA RSA
密钥长度 256 位 256 位 2048 位
签名速度 快 快 慢
验签速度 快 快 较慢
加密速度 中等(混合加密) 不支持 慢
安全性基础 椭圆曲线离散对数 椭圆曲线离散对数 大整数分解
国密合规 是 否 否

我的建议: 如果系统从零开始,直接上 SM2。如果是存量 RSA 系统迁移,优先做签名验签的替换——这部分改动最小,收益最大。加密场景可以后做,因为涉及对称密钥派生逻辑,改动面更大。