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 哈希算法。基本流程是这样的:
- 输入:共享秘密 Z(字节串)、派生长度 klen(位)
- 初始化计数器 ct = 0x00000001
- 循环计算:Ha = SM3(Z || ct),ct 自增
- 拼接所有 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 里的密钥派生实现时,就不会觉得突兀。