4. SM2椭圆曲线公钥密码算法
SM2算法,说白了就是咱们国密体系里的"非对称加密担当"。我最早接触它是在一个政务系统的身份认证项目里,当时甲方明确要求不能用RSA,必须上SM2。嗯,那会儿我还不太熟悉椭圆曲线,踩了不少坑。今天咱们就把这块硬骨头啃下来。
4.1 数学基础:椭圆曲线与有限域
先别被"椭圆曲线"这个名字吓到。它跟椭圆没什么关系,只是因为描述它的方程长得像计算椭圆周长的积分式。SM2使用的椭圆曲线方程是:
y² = x³ + ax + b (mod p)
这里的 p 是一个大素数,a 和 b 是系数。整个曲线定义在有限域 Fp 上——说白了,就是所有坐标值都在 0 到 p-1 之间取整数,做完运算还要取模。
SM2标准曲线参数(国密局推荐):
- 素数 p = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
- 系数 a = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
- 系数 b = 0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
- 基点 G = (0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7, 0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0)
- 阶 n = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
椭圆曲线上有个神奇的性质:两个点可以"相加",结果还是曲线上的点。这个加法规则不是普通的坐标相加,而是通过几何方法定义的——过P和Q做直线,交曲线于第三点R',然后R'关于x轴的对称点就是P+Q。
我刚开始学的时候,觉得这个加法规则太绕了。后来在项目中写SM2签名验签代码时,才真正理解为什么需要这样设计——它保证了运算的封闭性和安全性。
4.2 SM2密钥对生成
密钥生成其实很简单,但背后有大学问。流程如下:
- 随机选择一个整数 d,范围在 [1, n-2] 之间
- 计算公钥 P = d * G(椭圆曲线上的标量乘法)
- 私钥就是 d,公钥就是 P
这里的关键是"标量乘法"——把私钥 d 当作倍数,对基点 G 做 d 次加法。听起来很慢对吧?实际上用"倍点-加法"算法,256位的私钥只需要几百次运算。
我的经验:生成随机数 d 时,一定要用密码学安全的随机数生成器。我曾经见过有人用 rand() 生成私钥,结果被攻击者用暴力枚举猜出来了。在Java里用 SecureRandom,在OpenSSL里用 RAND_bytes,别图省事。
公钥P是一个点,包含x和y两个坐标。SM2标准中,公钥通常以"04 || x || y"的格式存储,其中"04"是未压缩标识符。压缩格式可以只存x坐标和y的奇偶性,能省一半空间。
4.3 SM2加密解密流程
SM2加密解密,我习惯把它理解成"用椭圆曲线做密钥交换+对称加密"的混合体。具体流程:
加密过程
- 生成临时密钥对 (k, R = k * G)
- 计算共享点 S = k * P(P是接收方公钥)
- 从S的x坐标派生出对称密钥(通过KDF函数)
- 用对称密钥加密明文,得到密文
- 输出:R || 密文 || 杂凑值
解密过程
- 从密文中取出R
- 用私钥d计算共享点 S' = d * R
- 从S'的x坐标派生出同样的对称密钥
- 用对称密钥解密密文,得到明文
避坑指南:我曾经在实现KDF(密钥派生函数)时,忘记检查共享点S是否为无穷远点。如果S是无穷远点,派生出来的密钥全是0,加密就形同虚设。SM2标准里明确要求检查S != O,千万别漏。
你想想看,为什么SM2不直接用椭圆曲线加密明文?因为椭圆曲线运算太慢了,256位的密钥加密1KB的数据要算好多次标量乘法。用混合加密,对称部分用SM4,速度能快几百倍。
4.4 SM2签名验签流程
签名验签是SM2最常用的功能。我在做身份认证方案时,几乎每个环节都要用到它——设备认证、用户登录、数据完整性校验。
签名过程
- 计算消息杂凑值 e = SM3(Z || M),其中Z包含用户身份信息
- 生成随机数 k,计算椭圆曲线点 (x1, y1) = k * G
- 计算 r = (e + x1) mod n,如果 r == 0 或 r + k == n 则重新生成k
- 计算 s = ((1 + d)⁻¹ * (k - r * d)) mod n,如果 s == 0 则重新生成k
- 输出签名 (r, s)
验签过程
- 检查 r, s 是否在 [1, n-1] 范围内
- 计算 e = SM3(Z || M)
- 计算 t = (r + s) mod n,如果 t == 0 则验签失败
- 计算椭圆曲线点 (x1', y1') = s * G + t * P
- 计算 R = (e + x1') mod n,如果 R == r 则验签通过
关键点:SM2签名中引入了用户身份信息Z(包含用户ID、公钥、曲线参数等),这能防止"密钥替换攻击"。我在做跨系统认证时,就遇到过因为Z计算不一致导致验签失败的情况——两个系统对用户ID的编码方式不同,一个用UTF-8,一个用GBK,结果Z不一样,签名验不过。
下面这张图展示了SM2签名验签的整体流程:
SM2签名验签有个细节值得注意:签名过程中如果 r == 0 或 s == 0 或 r + k == n,都要重新生成 k。为什么?因为一旦这些条件成立,攻击者可能从签名中推导出私钥。我在做安全审计时,就发现过某个开源库没做这些检查,赶紧提了issue。
4.5 性能对比与选型建议
最后,我整理了一张SM2与RSA的对比表,方便你在做方案设计时参考:
| 对比项 | SM2(256位) | RSA(2048位) |
|---|---|---|
| 密钥长度 | 32字节(私钥)/ 64字节(公钥) | 256字节(私钥)/ 256字节(公钥) |
| 签名速度 | 快(约2000次/秒) | 慢(约200次/秒) |
| 验签速度 | 快(约1000次/秒) | 快(约2000次/秒) |
| 加密速度 | 中等(混合加密) | 慢(直接加密) |
| 安全强度 | 128位 | 112位 |
| 国密合规 | 是 | 否 |
我的建议:如果是新项目,尤其是面向政务、金融等需要国密合规的场景,直接上SM2。如果是老系统改造,可以考虑SM2与RSA双证书并行过渡。我在一个银行项目中就是这么做的——先用RSA跑业务,同时签发SM2证书做灰度验证,等所有客户端都升级了再切过去。