SM2密钥对生成:私钥与公钥的生成算法归纳总结

密钥对生成,说白了就是两个动作——随机数抽取和椭圆曲线点乘。很多人以为这事很简单,不就是调用个库吗?我在项目中见过好几次因为密钥对生成不规范导致的安全漏洞,最后排查发现是随机数质量不行或者没做边界检查。嗯,这章我们就抠开细节看看。

私钥生成:核心是随机数

SM2的私钥其实就是一个大整数,记作d,取值范围有严格规定:
d ∈ [1, n-2],其中n是SM2椭圆曲线的阶(推荐使用sm2p256v1时,n = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123)。

为什么不能取0、n-1或n?因为d=0会导致公钥为无穷远点,d=n或n-1对应的公钥分别是无穷远点或对称点,这些都不具备密码学意义。我曾经在测试环境里用了一条未检查的随机数生成器,结果跑出一对全零私钥……幸好是开发阶段,否则后果不堪设想。

我的个人经验:私钥生成必须搭配合格的随机数源。在服务端优先使用 /dev/urandom 或系统级DRBG;在嵌入式设备上要格外小心,我曾经碰过芯片硬件的硬件随机数发生器(TRNG)输出稳定度不足,导致多个设备生成了相同私钥。建议生成后做一次自相关校验。

私钥生成的流程大致如下:

  1. 调用安全随机数生成器获取一个长度为 ceil(log2(n)) 的字节串
  2. 将字节串转换为大整数 d
  3. 检查 d 是否在 [1, n-2] 区间内
  4. 如果不在,重新生成(最多尝试若干次,防止死循环)
  5. 输出私钥 d

注意:不要直接用 random.randint 或 numpy.random 这类非密码学安全随机数生成器。国密标准要求至少使用符合 GM/T 0005-2012 的随机数生成模块。

公钥生成:椭圆曲线点乘运算

有了私钥d,公钥的计算公式非常简单:
P = d * G
其中G是SM2椭圆曲线的基点(base point),点乘运算是椭圆曲线上的标量乘法。

你想想看,d是整数,G是点,结果P也是曲线上的一个点。这个运算不可逆——从P反推d就是所谓的“椭圆曲线离散对数问题”,目前还没有多项式时间算法。这也是SM2安全性的基石。

在实现时,点乘通常采用“双倍-加”(Double-and-Add)算法,或者为了抵抗侧信道攻击,会使用蒙哥马利阶梯(Montgomery Ladder)。我早期做的一个SM2签名服务,起先用了简单的双倍-加,结果功耗分析工具一跑就能猜出密钥高位……后来改成蒙哥马利阶梯才通过国密认证。

# 伪代码:SM2点乘运算(使用蒙哥马利阶梯)
def point_multiply(d, G):
    # 初始化两个点:R0 = O(无穷远点),R1 = G
    R0 = O
    R1 = G
    # 从最高位到最低位扫描d的二进制
    for bit in bits_of_d_from_msb_to_lsb:
        if bit == 0:
            R1 = add(R0, R1)    # 点加
            R0 = double(R0)      # 倍点
        else:
            R0 = add(R0, R1)
            R1 = double(R1)
    return R0

公钥输出时,需要以未压缩格式或压缩格式进行编码:

  • 未压缩格式:04 || x坐标(32字节) || y坐标(32字节),共65字节
  • 压缩格式:02/03 || x坐标(32字节),共33字节(y坐标通过奇偶性恢复)

两者对比如下:

格式标识字节总长度解码复杂度常见场景
未压缩0x0465字节低(直接读取xy)证书、密钥交换
压缩0x02(y为偶)或0x03(y为奇)33字节高(需计算平方根)存储空间受限设备

关键点:公钥必须属于椭圆曲线,即需要验证 y^2 ≡ x^3 + ax + b (mod p)。我在项目里发现过公钥数据被截断导致校验失败的情况,所以解码后建议做一次曲线方程验证。

算法校验与安全性检查

密钥对生成完毕后,不能直接用,需要过几道检查:

  • 私钥非零且小于n-1: 上一步已经做了
  • 公钥非无穷远点: 无穷远点不能用于签名/加密
  • 公钥的坐标在模p范围内: 防止曲线攻击
  • 公钥满足曲线方程: 确保是合法点
  • 随机数质量验证: 可以做个简单连续重复测试(比如同一字节序列出现太多次则报警)

我曾经踩过的坑:有次上线前做非功能验证,发现一批SM2密钥对中有3个公钥完全一样。排查下来是Docker容器启动时熵池初始化太慢,导致前几个密钥用了相同种子。解决方案是在应用层加一个 os.urandom 预读取缓冲区,积累足够熵后再生成。

密钥对生成流程总览

下图把整个过程串起来了,你可以保存下来当检查清单:

SM2密钥对生成流程图 开始生成密钥对 调用安全随机数发生器 生成私有密钥d ∈ [1, n-2] 检查:d者否在 [1, n-2]? 且随机数质量合格? 是 否 重新生成私钥 重新生成(最多尝试10次) 计算公钥 P = d * G (蒙哥马利阶梯点乘) 输出密钥对 私钥(64字节Hex) + 公钥(未压缩65字节) 说明:私钥检查失败返回重新生成,循环上限建议设为10次防止无限循环。

整个流程下来,你其实只需要关注两个核心:随机数质量和点乘实现正确性。我个人习惯在生成后立刻做一个自检测——用新生成的私钥对一段固定数据签名,再用公钥验签,通过才算合格。这个额外步骤能暴露绝大多数实现错误。