3. SM2 算法概述:国密标准体系、曲线参数与密钥对生成

聊到 TEE 里的密钥派生,SM2 是绕不开的一环。我个人习惯把 SM2 看作「国密体系里的 ECDSA + ECDH 合体版」——它既做签名验签,也做密钥交换,还支持加密解密。说白了,一条曲线包办了非对称密码的三大核心场景。

嗯,咱们先捋清楚它的来龙去脉,再动手看密钥对到底怎么算出来的。

3.1 国密标准体系:SM2 在哪儿站着

国密标准不是只有 SM2 一个孤零零的算法。它是一整套密码工具链。我整理了一张表,方便你对照着看:

标准编号 算法名称 核心用途 对标国际算法
GB/T 32918 SM2 签名、密钥交换、加密 ECDSA / ECDH / ECIES
GB/T 32905 SM3 密码杂凑(哈希) SHA-256
GB/T 32907 SM4 对称分组加密 AES-128
GB/T 36968 SM9 基于标识的密码 IBC / BF-IBE

你在 TEE 里做密钥派生,最常打交道的组合就是 SM2 + SM3。SM2 负责非对称层的密钥对生成和签名,SM3 负责派生时的哈希压缩。我在项目里见过有人只用 SM2 不做 SM3 摘要,结果签名长度失控——嗯,这个坑后面会细讲。

3.2 SM2 曲线参数:一条精心设计的椭圆曲线

SM2 使用的椭圆曲线定义在素域 Fp 上,参数是国密局公开的。你想想看,如果曲线参数有后门,整个密码体系就塌了。所以这些参数都是公开可验证的,没有任何「魔法数字」。

核心参数如下:

参数 值(十六进制) 说明
p FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF 素域模数,256 位
a FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC 曲线方程 y² = x³ + ax + b 中的 a
b 28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93 曲线方程中的 b
Gx 32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7 基点 G 的 x 坐标
Gy BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0 基点 G 的 y 坐标
n FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123 基点 G 的阶(素数)

关键点:SM2 的 p 和 n 都是 256 位,但 n 略小于 p。这意味着私钥空间是 2²⁵⁶ 量级,暴力破解在可预见的未来完全不现实。

我记得第一次手动验证这些参数时,用 OpenSSL 的 EC 命令逐项比对,发现 G 点坐标完全吻合——嗯,心里踏实了。曲线方程是 y² = x³ + ax + b,a 的值是 p - 3,这是为了加速点运算做的优化。

3.3 SM2 密钥对生成原理:从随机数到公钥

密钥对生成其实就三步。我习惯用「掷骰子 → 乘法 → 检查」来记:

  1. 生成私钥 d:在 [1, n-2] 范围内取一个安全的随机数。
  2. 计算公钥 P:P = d · G,即私钥乘以基点 G 的标量乘法。
  3. 验证:检查 P 是否在曲线上,且不是无穷远点。

你可能会问:为什么私钥范围是 [1, n-2] 而不是 [1, n-1]?因为 d = n-1 时,d · G = -G,虽然合法但容易在实现中引入侧信道风险。国密标准直接把这个值排除掉了。

下面这张图把流程画清楚了:

SM2 密钥对生成流程 安全随机数发生器 d ∈ [1, n-2] 标量乘法 P = d · G 曲线验证 P 在曲线上? 输出密钥对 私钥 d(256 位随机整数) 公钥 P = (xP, yP)(两个 256 位坐标) ⚠ 私钥必须使用硬件真随机数或经认证的 DRBG 生成 在 TEE 环境中,建议直接调用安全随机数 API

实战建议:在 TEE 里生成 SM2 私钥时,千万别用软件伪随机数。我见过一个案例,某厂商用 C 标准库的 rand() 生成私钥,结果两个设备产出了相同的密钥对——嗯,后果很严重。TEE 内部通常提供 TEE_GenerateRandom(),直接用那个。

3.4 标量乘法的实现要点

P = d · G 是整个密钥对生成的计算瓶颈。d 是 256 位的随机数,G 是曲线上的固定点。这个运算不能直接算 d 次加法——2²⁵⁶ 次加法,宇宙毁灭都算不完。

实际用的是 双倍-加(Double-and-Add) 算法。我简单说一下思路:

  • 把 d 写成二进制,比如 d = (d₂₅₅ d₂₅₄ ... d₀)₂
  • 从最高位开始,每步先做一次点倍(2 × Q)
  • 如果当前位是 1,再做一次点加(Q + G)

代码实现大概长这样:

// SM2 标量乘法:P = d · G
// 输入:私钥 d(256 位大整数),基点 G
// 输出:公钥 P(曲线上的点)

sm2_point_t scalar_mult(sm2_bn_t d, sm2_point_t G) {
    sm2_point_t Q = INFINITY;  // 初始化为无穷远点
    sm2_point_t R = G;

    // 从最高位到最低位遍历
    for (int i = 255; i >= 0; i--) {
        Q = point_double(Q);   // 点倍:Q = 2 * Q
        if (test_bit(d, i)) {  // 如果当前位为 1
            Q = point_add(Q, R); // 点加:Q = Q + G
        }
    }
    return Q;
}

注意:上面的代码是教学简化版。生产环境必须使用 恒定时间(constant-time) 实现,否则通过功耗或电磁分析就能把私钥 d 的每一位猜出来。我在 TEE 里做审计时,发现过开源库的标量乘法没有做恒定时间处理——那个漏洞可以直接用简单功耗分析(SPA)攻破。

3.5 公钥的编码格式

SM2 公钥是一个曲线上的点,包含 x 和 y 两个 256 位坐标。国密标准定义了两种编码方式:

格式 长度 说明
未压缩格式 65 字节 0x04 || x || y,最常用
压缩格式 33 字节 0x02/0x03 || x,通过 x 恢复 y

在 TEE 内部存储时,我建议用未压缩格式。虽然多 32 字节,但省去了恢复 y 坐标的计算开销。TEE 的内存通常够用,计算资源反而更宝贵。

嗯,到这里 SM2 的曲线参数和密钥对生成原理就讲清楚了。你想想看,整个流程其实就依赖一个安全的随机数和一个高效的标量乘法。后面讲 TEE 内密钥派生时,我们会把私钥 d 换成派生因子,但数学内核完全一样。