SM2 密钥对生成
密钥对生成,说白了就是选一个随机数当私钥,再算出对应的公钥。SM2 基于椭圆曲线,这东西比 RSA 高效不少。我一直觉得,理解参数比背公式更重要。
椭圆曲线参数
SM2 用的是国标推荐的曲线参数。全是固定值,你不需要自己推导。我刚开始学的时候总想自己造一条曲线,后来被同事拦住了——造曲线容易挖坑,标准参数经过验证,直接用就好。
SM2 曲线定义在素域 Fp 上,方程为:
y² = x³ + ax + b (mod p)
关键参数如下:
| 参数 | 说明 | 值(十六进制) |
|---|---|---|
| p | 素数模数,256 位 | FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF |
| a | 方程系数 | FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC |
| b | 方程系数 | 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93 |
| G | 基点(x, y 坐标) | 32C4AE2C 1F198119 5F990446 6A39C994 … |
| n | 基点的阶(质数) | FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123 |
| h | 余因子 | 01 |
核心要点:n 的长度和 p 一样都是 256 位,保证了安全性。我用 SM2 做过一次签名模块,如果 n 选错,密钥对可能无法通过标准验证。
密钥生成算法
密钥生成流程并不复杂。私钥是一个随机大整数 dA,范围在 [1, n-2] 之间。公钥是 PA = dA · G(椭圆曲线点乘)。
具体步骤:
- 使用密码学安全的随机数生成器产生 dA。
- 检查 dA 是否在合法区间内。
- 计算公钥点 PA = dA · G。
- 验证 PA 不是无穷远点。
- 输出 (dA, PA)。
我用 Python 写过一段演示代码,你可以参考:
# SM2 密钥对生成(示意)
from sm2 import SM2Curve, random_bigint
curve = SM2Curve()
dA = random_bigint(1, curve.n - 2) # 随机私钥
PA = curve.scalar_mult(dA, curve.G) # 公钥 = dA * G
# 输出
print("私钥:", dA.hex())
print("公钥:", (PA.x.hex(), PA.y.hex()))
小技巧:实际工程中不建议自己实现点乘,一定要用经过审计的密码库,比如 OpenSSL、GmSSL、Bouncy Castle。我曾经自己写过一个点乘,因为 skip 了一次加法公式的边界条件,导致公钥偶尔算错——排查了一整天才发现问题。
随机数质量要求
私钥的安全完全依赖随机数质量。如果随机数可预测,私钥就等于白送。SM2 标准明确要求使用符合 GB/T 32918.5 的随机数发生器。
有哪些坑?我列一下:
- 熵源不足:嵌入式设备容易缺真随机数,别只用系统时间 + PID 凑数。
- 相同随机数:两个用户不能生成一样的私钥。概率极低,但万一发生,整个系统完蛋。
- 伪随机算法弱:比如用常规的 rand(),周期太短,不安全。
⚠ 曾经踩过的坑:某次在 IoT 设备上集成 SM2,随机数我用了硬件 TRNG,但忘了检查输出是否全零。结果有千分之一的概率私钥全是 0x00……公钥直接算出来全是 0。从那以后我不光加检查,还定期做 SP800-22 随机性测试。
随机数质量要求简表:
| 要求 | 说明 |
|---|---|
| 不可预测性 | 攻击者无法通过历史输出推测未来私钥 |
| 独立同分布 | 每次生成独立,且符合均匀分布 |
| 抗侧信道 | 随机数生成过程不泄漏内部状态 |
| 通过统计测试 | 推荐通过 NIST SP800-22 / GM/T 0005 测试 |
你想想看,如果随机数生成器被旁路攻击,整个密钥体系就形同虚设。所以我在项目里一般会双路混合:硬件 TRNG 做种子,再用 CTR_DRBG 扩展输出。既保证熵,又保证吞吐。
最后提醒一点:私钥一定要安全存储。生成后如果明文落盘,等于把门钥匙放在门口垫子下。我习惯用硬件安全模块(HSM)或 CPU 安全区(TEE)保护私钥。生成阶段就别让它离开安全边界。