1. SM2 算法概述:国密标准背景、椭圆曲线参数与算法对比

各位同学,咱们今天正式开讲《SM2 算法在 TEE 中的密钥派生》这门课。第一章,我打算先带大家把 SM2 算法的底子摸清楚。说白了,你要在 TEE 里用 SM2 做密钥派生,首先得知道 SM2 是什么、它从哪来、它凭什么能干活。

我最早接触国密是 2015 年,那时候项目要求必须过国密局检测。说实话,一开始我也觉得 SM2 不就是个 ECC 变种嘛,后来踩了几个坑才明白——细节决定成败。咱们今天就把这些细节掰开揉碎了讲。

1.1 国密标准背景

SM2 是国家密码管理局发布的椭圆曲线公钥密码算法标准,标准编号是 GM/T 0003-2012。它包含三个部分:数字签名算法、密钥交换协议、公钥加密算法。咱们这门课主要关注密钥派生,但底层用的就是 SM2 的椭圆曲线群运算。

为什么要搞国密?原因很简单——自主可控。以前咱们大量使用 RSA 和 ECDSA,这些算法虽然成熟,但核心参数和实现都掌握在别人手里。2010 年前后,国家开始推动商用密码算法国产化,SM2 就是那个阶段诞生的。

关键时间线:

  • 2010 年:SM2 算法研制完成
  • 2012 年:正式发布为国家标准 GM/T 0003-2012
  • 2017 年:SM2 成为 ISO/IEC 14888-3 国际标准的一部分
  • 2020 年:等保 2.0 强制要求关键信息系统使用国密算法

嗯,这里要注意:SM2 不是简单的 ECC 复制品。它在曲线参数选择、签名流程、密钥派生方式上都有自己的一套逻辑。我在一个金融项目中遇到过,有人直接把 OpenSSL 的 ECDSA 代码改个名字就当 SM2 用,结果验签死活过不去——因为 SM2 的签名哈希计算方式跟 ECDSA 不一样。

1.2 SM2 椭圆曲线参数

SM2 使用一条特定的 256 位椭圆曲线,参数由国密标准直接指定。你想想看,如果曲线参数可以随便选,那安全性就没法保证了。所以 SM2 把参数固定死,大家用同一套,省心也安全。

具体参数如下:

参数 值 说明
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 时,把参数写成一个常量结构体,不要每次计算都从字符串解析。我在一个嵌入式项目里见过有人每次签名都重新解析参数,结果性能直接掉了 30%。

这条曲线的特点是:p 接近 2²⁵⁶,但又不是标准的 2²⁵⁶ - 某个小值。这意味着模约简不能直接用 Montgomery 的经典方法,需要做一些调整。咱们后面讲 TEE 实现时会专门聊这个。

1.3 SM2 与 ECC / RSA 的对比

很多同学会问:SM2 跟 ECC 到底啥关系?跟 RSA 比又怎么样?我直接说结论:SM2 是一种特定的 ECC,但它在签名流程、密钥格式、哈希使用方式上跟国际标准的 ECDSA 有区别。

咱们用一张表来看清楚:

对比维度 SM2 ECDSA (NIST P-256) RSA-2048
算法类型 椭圆曲线 椭圆曲线 大整数分解
密钥长度 256 位 256 位 2048 位
签名长度 64 字节 64 字节 256 字节
签名速度 快 快 慢
验签速度 快 快 快(相对签名)
密钥生成 极快 极快 慢
哈希使用 SM3(必选) SHA-256(常用) SHA-256(常用)
标准化 国标 GM/T 0003 FIPS 186-4 PKCS #1
专利状态 国内自主 Certicom 专利 已过期

从这张表能看出什么?SM2 和 ECDSA 在密钥长度、签名长度上完全一致,都是 256 位曲线、64 字节签名。但 SM2 强制绑定 SM3 哈希算法,而 ECDSA 通常用 SHA-256。这个区别在密钥派生中会直接影响派生函数的输入构造方式。

避坑指南: 我曾经在一个项目中看到有人用 SM2 签名但哈希用的是 SHA-256,结果国密检测直接不通过。标准规定 SM2 必须搭配 SM3,没有商量余地。你在 TEE 里做密钥派生时,派生函数也必须用 SM3,别想着偷懒复用 SHA-256 的代码。

跟 RSA 比,SM2 的优势就更明显了。RSA-2048 的密钥是 SM2 的 8 倍,签名是 4 倍,而且 RSA 密钥生成慢得离谱——我在嵌入式设备上生成一次 RSA-2048 密钥对,最慢的一次等了 40 秒。SM2 呢?毫秒级搞定。在 TEE 这种资源受限的环境里,这个差距是致命的。

但 RSA 也有它的优势:兼容性好,几乎所有老旧系统都支持。如果你要跟 legacy 系统对接,RSA 可能更省事。不过咱们这门课聚焦 TEE 场景,我建议你优先考虑 SM2。

1.4 SM2 密钥派生核心逻辑

咱们这门课叫《SM2 算法在 TEE 中的密钥派生》,所以得先让大家明白:密钥派生在 SM2 里是怎么玩的?

SM2 标准中定义了一个密钥派生函数 KDF,它基于 SM3 哈希算法。基本流程是这样的:

  1. 输入:共享秘密 Z(字节串)、派生长度 klen(位)
  2. 初始化计数器 ct = 0x00000001
  3. 循环计算:Ha = SM3(Z || ct),ct 自增
  4. 拼接所有 Ha,截取前 klen 位作为派生密钥

代码实现大概长这样:

// SM2 KDF 核心实现(伪代码)
int sm2_kdf(const uint8_t *Z, uint32_t Z_len, 
            uint32_t klen, uint8_t *K) {
    uint32_t ct = 0x00000001;
    uint32_t hash_len = 32;  // SM3 输出 32 字节
    uint32_t d = (klen + 7) / 8;  // 目标字节数
    uint32_t n = (d + hash_len - 1) / hash_len;
    
    for (uint32_t i = 0; i < n; i++) {
        uint8_t input[Z_len + 4];
        memcpy(input, Z, Z_len);
        input[Z_len + 0] = (ct >> 24) & 0xFF;
        input[Z_len + 1] = (ct >> 16) & 0xFF;
        input[Z_len + 2] = (ct >> 8) & 0xFF;
        input[Z_len + 3] = ct & 0xFF;
        
        sm3_hash(input, Z_len + 4, &K[i * hash_len]);
        ct++;
    }
    return 0;
}

注意: 计数器 ct 是大端序的 4 字节,这个细节很容易搞错。我见过有人用小端序实现,结果派生出来的密钥跟标准对不上。调试了整整两天才发现是字节序的问题。

这个 KDF 在 TEE 里实现时,有个关键问题:共享秘密 Z 从哪里来?通常来自 SM2 密钥交换协议或者 ECDH 协商。咱们后面几章会详细讲如何在 TEE 的安全环境中安全地生成和管理这个 Z 值。

好了,第一章的内容就到这里。SM2 的背景、参数、对比、KDF 核心逻辑,这些是后续所有章节的基础。你把这些吃透了,后面讲 TEE 里的密钥派生实现时,就不会觉得突兀。

SM2 算法知识体系 SM2 算法 国密标准背景 GM/T 0003-2012 ISO/IEC 14888-3 椭圆曲线参数 256 位素数域 固定基点 G 与阶 n 与 ECC/RSA 对比 密钥长度 / 签名速度 哈希绑定 SM3 密钥派生函数 KDF 输入:共享秘密 Z SM3 哈希迭代 输出:派生密钥