4. SM2椭圆曲线公钥密码算法
SM2 算法,说白了就是咱们国家自己的椭圆曲线公钥密码体系。我刚开始接触国密时,总觉得它跟 ECDSA、ECDH 差不多,后来踩了几个坑才发现——细节上差别还真不小。这一章咱们就把 SM2 的数学基础、密钥生成、签名验签和加解密流程掰开揉碎了讲。
4.1 SM2 的数学基础——椭圆曲线群
SM2 使用的椭圆曲线方程是:
y² = x³ + ax + b (mod p)
其中参数由国家标准定义。我习惯把椭圆曲线群理解成一个“加法器”——点与点之间可以做加法运算,但结果还在曲线上。这个性质是 SM2 所有密码操作的基础。
| 参数 | SM2 推荐值(部分) | 说明 |
|---|---|---|
| p | 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF | 素数模数,256 位 |
| a | 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC | 曲线系数 |
| b | 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93 | 曲线系数 |
| G | 基点坐标 (Gx, Gy) | 生成元,阶为 n |
| n | 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123 | 基点的阶 |
我的经验: 参数 p 和 n 非常接近,这意味着 SM2 的曲线几乎达到了 256 位素数的最大安全容量。我在做硬件加速时,利用这个特性优化过模约减算法,性能提升了约 15%。
4.2 SM2 密钥对生成
密钥生成其实就三步,但每一步都有坑。我直接上代码:
// SM2 密钥对生成(伪代码)
输入:椭圆曲线参数 (p, a, b, G, n)
输出:私钥 d, 公钥 P
1. 生成随机数 d ∈ [1, n-2] // 私钥
2. 计算 P = d * G // 公钥,标量乘法
3. 返回 (d, P)
// 实际实现时要注意:
// - 随机数生成器必须使用真随机源(如 /dev/random)
// - 标量乘法要防侧信道攻击(定时攻击、功耗分析)
避坑指南: 我曾经在嵌入式设备上直接用 libc 的 rand() 生成私钥,结果被同事骂惨了——rand() 的周期太短,攻击者可以预测私钥。后来全部换成了硬件 TRNG。
公钥 P 是一个椭圆曲线上的点,包含 x 和 y 两个坐标。SM2 公钥通常以 04 || x || y 的格式存储(未压缩),或者 02/03 || x(压缩格式)。我个人建议用未压缩格式,虽然多 32 字节,但省去了解压缩的计算开销。
4.3 SM2 签名验签流程
SM2 签名算法跟 ECDSA 最大的区别在于——它用了两次哈希,而且签名结果是一个 (r, s) 对。你想想看,为什么要这么设计?
我画了一张流程图,帮你理清签名和验签的完整路径:
SM2 签名 vs ECDSA 签名:
- SM2 的 r 计算包含了消息哈希 e,而 ECDSA 的 r 只依赖随机数 k
- SM2 的 s 计算需要 (1+d)⁻¹,ECDSA 是 k⁻¹ · (e + r·d)
- SM2 验签用双标量乘法 s·G + t·P,ECDSA 需要两次标量乘法加一次求逆
4.4 SM2 加密解密流程
SM2 加密用的是混合加密——椭圆曲线密钥交换 + 对称加密。说白了,就是用 SM2 协商一个共享密钥,然后用 SM4 加密实际数据。我刚开始学的时候觉得绕,后来发现这种设计其实很聪明:非对称加密保护密钥,对称加密保护数据,各取所长。
加密流程大致如下:
- 生成临时密钥对: 发送方生成随机数 k,计算临时公钥 C₁ = k·G
- 计算共享密钥: 计算点 S = k·P(P 是接收方公钥),取 S 的 x 坐标作为共享密钥材料
- 派生对称密钥: 用 KDF(密钥派生函数)从共享密钥材料中派生出加密密钥和 MAC 密钥
- 加密数据: 用 SM4 加密明文,得到密文 C₂
- 计算校验值: 用 MAC 密钥计算 C₂ 的校验值 C₃
- 输出密文: C = C₁ || C₃ || C₂
我曾经踩过的坑: 在实现 SM2 解密时,忘记检查临时公钥 C₁ 是否在椭圆曲线上。结果攻击者可以传入一个不在曲线上的点,导致共享密钥泄露。后来我在代码里加了一行
if (!isOnCurve(C₁)) return ERROR;,从此再没出过问题。
解密流程就是加密的逆过程:
- 用私钥 d 计算 S = d·C₁,恢复共享密钥
- 用 KDF 派生对称密钥
- 先验证 C₃ 校验值,再解密 C₂
- 注意:一定要先验证后解密,否则可能被 padding oracle 攻击
| 步骤 | 加密方 | 解密方 |
|---|---|---|
| 密钥协商 | k·G → C₁, k·P → S | d·C₁ → S |
| 密钥派生 | KDF(S_x || S_y, klen) | KDF(S_x || S_y, klen) |
| 数据加密 | SM4 加密明文 → C₂ | SM4 解密密文 → 明文 |
| 完整性校验 | MAC(C₂) → C₃ | 验证 MAC(C₂) == C₃ |
我的建议: 实际项目中,SM2 加密很少直接用于大文件加密。更常见的做法是:
- 用 SM2 加密 SM4 的会话密钥(密钥封装)
- 用 SM4 加密实际数据
- 这样既利用了 SM2 的密钥分发能力,又发挥了 SM4 的加解密速度
嗯,SM2 的核心内容就这些。数学基础、密钥生成、签名验签、加解密——每个环节都有它的设计逻辑。我个人觉得,理解 SM2 最好的方式就是动手实现一遍,哪怕只是伪代码,也能帮你发现那些文档里没写的细节。