SM2 密钥对生成原理
咱们做国产密码算法,SM2 密钥对的生成就是一个绕不开的门槛。说白了,就是生成一对钥匙:一把私钥自己藏着,一把公钥丢出去给大家用。但它的数学底子比我们想象的优雅得多。
我当年第一次接触 SM2 时,被那个椭圆曲线点乘绕得云里雾里。后来在实际项目里踩过几次坑,才真正搞明白——其实核心就三个东西:一个随机数、一个基点、一个曲线方程。今天咱们就把它彻底拆开。
密钥参数:这些数字从哪来?
SM2 使用国家密码管理局发布的推荐参数。这些参数不是随便选的,它们定义了一条标准的椭圆曲线。你想想看,如果参数不统一,张三和李四生成的密钥对根本没法互通。
| 参数名 | 符号 | 说明 |
|---|---|---|
| 素数 | p | 有限域的模数,256 位素数 |
| 系数 | a, b | 椭圆曲线方程 y² ≡ x³ + ax + b (mod p) 中的系数 |
| 基点 | G | 曲线上一个公开的生成点 (Gx, Gy) |
| 阶 | n | 基点 G 的阶,即最小的正整数使得 n·G = O(无穷远点) |
关键点:SM2 的基点 G 的阶 n 是一个素数。这个性质保证了离散对数问题的困难性,也就是别人拿到你的公钥也推不出私钥。
我遇到过好几位新手,上来就自己随便写个 a、b 做实验,然后发现加解密结果对不上——因为没有用国标规定的参数。所以正式开发时,一定要从 SM2 标准文档里复制那串十六进制参数,少一个字节都不行。
私钥生成:小心你的随机数
私钥是什么?一个满足 1 ≤ d < n 的随机整数。就这么简单。但这里的“随机”两个字,水很深。
- 从操作系统获取真随机数(如 Linux 的
/dev/urandom)。 - 确保随机数在 [1, n‑1] 区间内。如果超出,要重新生成。
- 私钥 d 必须严格保密,永远不要明文存储。
极度危险:我曾经看到一个项目用 random.randint() 生成私钥,那根本不是密码学安全的随机源。攻击者如果猜到随机数种子,整个私钥就暴露了。永远用 os.urandom 或密码库自带的随机数生成器。
生成私钥的代码,我习惯这么写:
import os
def generate_private_key(n: int) -> int:
# n 是基点 G 的阶(素数)
byte_len = (n.bit_length() + 7) // 8
while True:
rand_bytes = os.urandom(byte_len)
d = int.from_bytes(rand_bytes, 'big')
if 0 < d < n:
return d
这个循环一般只跑一次就退出了,因为 n 接近 2²⁵⁶,d 落在正确区间的概率接近 100%。但安全起见,判断语句不能少。
公钥计算:一次华丽的大跳
公钥 P = d·G,就是私钥 d 乘以基点 G。这个乘法不是整数乘法,而是椭圆曲线上的标量乘法。说白了,就是把 G 对自己加 d 次。
你想想看,d 是个 256 位的数,真要每次加一次,加到宇宙毁灭都算不完。所以实际用的算法是“双倍加” (Double-and-Add)。它把 d 写成二进制,每次对结果翻倍、如果当前位是 1 就再加上 G。
效率小贴士:点加和点倍运算涉及有限域上的模逆、模乘。SM2 的推荐参数经过优化,可以用蒙哥马利模乘加速。在嵌入式设备上,一个私钥到公钥的计算大概只要几毫秒。
我来画一个直观的流程图,让你看清私钥到公钥的转化过程:
图中蓝色和紫色部分代表输入参数与随机数源,绿色是私钥,橙色是公钥。虚线箭头表示公钥计算还需要曲线参数和基点 G 的信息——这些参数在代码里通常会打包成一个结构体。
公钥的编码与展示
SM2 公钥通常以 04 开头的非压缩格式存储,后面紧跟 x 和 y 坐标。每个坐标 32 字节,所以总共 65 字节。当然也可以用压缩格式,只存 x 坐标加一个 y 的奇偶标记。
# 公钥编码示例(非压缩)
# 假设 Px, Py 是椭圆曲线上的坐标(大整数)
prefix = b'\x04'
pub_key_bytes = prefix + Px.to_bytes(32, 'big') + Py.to_bytes(32, 'big')
# 实际项目中可以用 openssl 的 EC_POINT_point2oct
经验之谈:我在一个跨平台对接的项目里,Android 端把公钥搞成了 33 字节压缩格式,而服务器只认 65 字节非压缩。两边折腾了两天才发现问题。建议在接口设计阶段就明确公钥编码格式,最好统一用非压缩加 04 前缀,兼容性最好。
小考:你能回答吗?
- 私钥取值范围是什么?为什么不能为 0?
- 公钥计算用到的标量乘法,为什么不能用普通整数乘法?
- 如果随机数生成器被植入后门,会发生什么?
想明白这三个问题,你就能理解为什么密码学这么强调“真随机”和“曲线参数不可篡改”。
好了,这一章的核心就这些。记住:私钥是随机数,公钥是点乘结果,参数是国标定的。链条只要断一环,整个密钥体系就垮了。