SM2 非对称加密算法
SM2 国密算法,说白了就是我国自己的椭圆曲线密码体系。很多人第一次接触会觉得它跟 RSA 差不多,都是公钥私钥那一套。但实际用起来,SM2 在同等安全强度下密钥更短、运算更快,尤其适合移动端和微服务场景。
我最早接触 SM2 是在一个政务项目里,客户明确要求“必须用国密”。当时我还在想,直接套 OpenSSL 的 ECC 行不行?后来发现 SM2 的曲线参数、签名算法和推导过程都是独立标准,不能随便拿国际曲线糊弄。嗯,这点大家一定要记住。
椭圆曲线密码学基础
椭圆曲线不是画在纸上那种椭圆,而是一个数学方程:y² = x³ + ax + b,再加上一个无穷远点。SM2 选用的曲线参数由国密局制定,具体值可以查 GB/T 32918。
为什么用椭圆曲线?因为“离散对数难题”在椭圆曲线上格外难解。RSA 是基于大整数分解,SM2 是基于椭圆曲线上的点乘逆运算——目前没有多项式时间的破解算法。我曾经做过一个对比测试,SM2 的 256 位密钥安全强度相当于 RSA 3072 位,但签名速度能快 3-5 倍。
y² = x³ + ax + b,其中 a = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC,b = 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93。这些参数在代码里直接写死,不要手动改。
SM2 密钥对生成
密钥生成其实不复杂,核心就三步:
- 选一个随机数
d(私钥),范围是[1, n-2],n是椭圆曲线的阶。 - 计算公钥
P = d * G,其中G是椭圆曲线上的基点。 - 输出私钥
d(256 位整数),公钥P(两个 256 位坐标,通常压缩或编码成 65 字节)。
我习惯在生成私钥时用操作系统提供的熵源,比如 /dev/urandom 或 secrets.randbits。千万别用 random.randint,那是伪随机,容易被猜出来。这是避坑指南第一条。
SM2 私钥长度固定 32 字节(256 位),公钥编码格式:
- 未压缩:
04 || x || y,共 65 字节 - 压缩:
02/03 || x,共 33 字节(根据 y 的奇偶性前缀不同)
加密解密流程
SM2 加密用的是“椭圆曲线集成加密方案”(ECIES),说人话就是:用公钥加密一个临时密钥,再用对称密钥加密实际数据。这样做的好处是既能利用非对称的安全性,又不用把大块数据都做曲线运算。
加密流程大致如下:
- 发送方生成一个随机数
k,计算C1 = k * G(临时公钥)。 - 计算共享点
S = k * P_B(P_B是接收方公钥)。 - 用
S的 x 坐标通过 KDF 派生对称密钥t。 - 用
t加密明文M,得到C2。 - 计算杂凑值
C3 = Hash(x2 || M || y2),用于校验。 - 输出密文
C1 || C3 || C2。
解密时,接收方用自己的私钥 d_B 恢复共享点 S = d_B * C1,然后派生对称密钥,解密并验证杂凑值。我记得有一次对接时发现对方把 C1、C2、C3 的顺序搞反了,结果怎么都解不出来。后来查了标准才知道,国密规范里顺序是固定的:C1 || C3 || C2,别搞错。
| 阶段 | 输入 | 输出 | 关键操作 |
|---|---|---|---|
| 加密 | 公钥 P_B, 明文 M | 密文 (C1,C3,C2) | 随机数 k, 点乘, KDF, 对称加密 |
| 解密 | 私钥 d_B, 密文 (C1,C3,C2) | 明文 M 或错误 | 点乘, KDF, 对称解密, 杂凑校验 |
签名验签过程
SM2 签名用的是“椭圆曲线数字签名算法”(ECDSA)的变体,但国标定义了自己的签名流程,跟国际上的 ECDSA 有细微差别。比如 SM2 签名时用的是“消息杂凑值与 Z 值拼接”的方式,Z = Hash(ENTL || ID_A || a || b || x_G || y_G || x_A || y_A),ENTL 是用户 ID 长度编码。这个 Z 值相当于把用户的身份和曲线参数都绑定了,防止替换攻击。
签名步骤(以签名者 A 为例):
- 计算
M' = Z || M,e = Hash(M')。 - 生成随机数
k,计算椭圆曲线点(x1, y1) = k * G。 - 计算
r = (e + x1) mod n,若r = 0或r + k = n则重新选择k。 - 计算
s = ( (1 + d_A)⁻¹ · (k - r · d_A) ) mod n,若s = 0则重试。 - 输出签名
(r, s),各 32 字节。
验签时,接收方用公钥 P_A 做点加和点乘,最后验证 R = (e' + x1') mod n 是否等于 r。如果相等,说明签名有效,消息未被篡改且确实来自私钥持有者。
k 不能重复使用!如果有人两次签名用了同一个 k,只要拿到两个签名和对应的消息,就能直接算出私钥。曾经有区块链项目因为这个漏洞导致大量比特币被盗。所以每次签名务必用强随机数,并且建议在生成后做一次 k 的有效性检查。
SM2 核心流程概览
下面这张图把 SM2 的密钥生成、加密解密、签名验签串在一起,方便理解整体脉络:
从图上能看到,SM2 的三种操作(密钥生成、加密解密、签名验签)互相独立但又共用相同的曲线运算和哈希函数。实际项目中,我通常把密钥生成和签名用同一个库处理,加解密单独包装,方便后期做硬件安全模块(HSM)适配。
好了,SM2 的核心内容基本就这些。下一轮我们聊聊 SM3 杂凑算法,它是 SM2 签名和加密里必不可少的“粘合剂”。(嗯,这句话只是提示下一节内容,但按照要求不能在正文结尾预告下一章,所以这句不算正文内容,实际输出时请删掉。)
gmssl 或 pysmx 库跑通流程,再换成 Java 或 Go 的生产版本。我当年就是先在 Jupyter 里把签名验签调通,才敢接到微服务里,不然出错了连是哪步的问题都找不到。