第1章 SM2算法概述
做密码学审计这些年,我接触过不少算法标准。SM2这个国密算法,说实话,刚入行时我总觉得它跟国际上的ECDSA差不多,后来深入才明白——细节里全是门道。今天我们先从最基础的「它到底是个什么东西」开始讲。
1.1 国密标准:SM2的来龙去脉
SM2是国家密码管理局发布的椭圆曲线公钥密码算法标准,正式编号是GM/T 0003-2012。它包含三个子协议:数字签名、密钥交换以及公钥加密。嗯,这是国内商用密码体系里的核心组件。
我在项目里见过不少团队,直接把OpenSSL里的ECDSA代码改个曲线参数就当SM2用,结果验签老失败。为什么?因为SM2在签名算法里多了一个公钥验证步骤,计算过程也有差异。标准这东西,差一个字节都不行。
国密标准家族里,SM2通常搭配SM3(密码杂凑算法)和SM4(分组密码算法)一起使用。你可以把它看作中国版的ECC实现,但它并非原样照搬国际标准。
我常用的参考文档:GM/T 0003-2012 全文约40页,重点看第5章(密钥生成)、第6章(签名生成与验证)和第7章(加密解密)。审计时你会发现,算法文档里的附录A列举了推荐的椭圆曲线参数,这个必须跟实际部署对照。
1.2 椭圆曲线密码体制:一个简单理解
椭圆曲线密码(ECC)的核心是一个数学难题:给定椭圆曲线上的两个点,求离散对数很困难。说白了,你知道公钥 P 和基点 G,想反推私钥 d(满足 P = d * G),计算量巨大。
SM2选用的是256位素域上的椭圆曲线,安全性对标RSA-3072。密钥短、性能好,是它的最大优势。
我记得有一次帮客户做密钥生成性能测试,对比过SM2和RSA:SM2生成一对密钥只要几毫秒,RSA-2048要几百毫秒。你想想看,在物联网设备上,这差距就是能不能用的区别。
曲线方程很简单:y² = x³ + ax + b (mod p)。SM2标准里固定了 a、b、p、基点 G 和阶 n。审计时这些参数不能乱改,否则安全性会崩塌。
关键参数表(来自GM/T 0003-2012 附录A)
| 参数 | 说明 | SM2推荐值 |
|---|---|---|
| p | 素数(256 bit) | FFFFFFFE FFFFFFFF ... |
| a | 曲线系数 | FFFFFFFE ... 00000000 |
| b | 曲线系数 | 28E9FA9E ... 00000001 |
| Gx | 基点x坐标 | 32C4AE2C ... |
| Gy | 基点y坐标 | BC3736A2 ... |
| n | 基点阶(素数) | FFFFFFFE ... 7203DF6B |
这些值都是16进制表示的,审计时要用工具逐个校验,千万别偷懒。
1.3 密钥对结构:私钥是数,公钥是点
SM2密钥对由两部分组成:
- 私钥 (d):一个在 [1, n-2] 范围内的随机整数。长度256位,不可预测。
- 公钥 (P):椭圆曲线上的一个点,满足
P = d * G。用非压缩格式时,包含两个256位的坐标(x, y),总共512位。
我曾在一个项目中遇到公钥存储只保留了x坐标的情况——美其名曰“节省空间”。结果签名验证时程序需要用x去解y,还要判断y的奇偶,增加了计算量不说,万一解出来两个点,代码选错了就完蛋。正规做法是存完整公钥或带标识的压缩格式。
密钥对的生成步骤其实很简单:
- 获取系统随机数生成器(真随机或密码学安全伪随机)。
- 产生256位随机数
d,检查范围并确保不为0或n-1。 - 计算标量乘法
P = d * G。 - 输出私钥数据
d和公钥数据(x, y)。
# SM2密钥生成伪代码(Python风格)
from sm2 import EC, RandomGenerator
def generate_sm2_keypair():
rng = RandomGenerator() # 必须使用国密认证的随机源
d = rng.random_bits(256)
while d == 0 or d >= n-1:
d = rng.random_bits(256)
P = ec_point_mul(d, G) # 标量乘法
return (d, P.x, P.y)
注意:实际代码中,随机数生成器的质量直接影响密钥安全。我曾审计过一个项目,用的rand()函数,导致私钥可预测——那真的是灾难。一定要用硬件随机数或通过国密检测的软件随机源。
知识体系一览
我把这一章的核心知识点画成了结构图,方便你对照记忆。图中从左到右展示了SM2的层次:标准规范→数学体制→密钥对组成→关键参数。
图里用虚线连接的底部参数,是椭圆曲线体制的固定值。审计时请对照标准原值,我见过有厂商误用了secp256r1的参数,导致SM2曲线变成国际曲线——标准不匹配,合规审查直接通不过。
密钥对的数据格式
实际存储或传输时,SM2密钥对一般按照ASN.1 DER编码封装。私钥格式引用PKCS#8,公钥格式包含一个标识符 0x04 后接两个256位的大整数。
我用一个简单的表格对比一下常见的格式差异:
| 格式 | 私钥存储 | 公钥存储 | 典型长度 |
|---|---|---|---|
| 原始二进制 | 256位 | 512位 (非压缩) | 32 / 64 字节 |
| PEM (PKCS#8) | Base64 + 头尾 | Base64 + 头尾 | 200~300 字节 |
| DER (X.509) | 需封装 | SubjectPublicKeyInfo | 91 字节左右 |
我自己的审计习惯:在代码中直接使用原始二进制格式时要格外小心字节序,SM2标准规定大端序(big-endian)。曾经有一个C语言项目用了小端序,结果密钥对完全算不对——找了两天bug才发现。
好了,SM2的基本概念先梳理到这。你会发现,标准、数学、密钥结构这三块是互相咬合的,任何一处理解偏差都会导致后续审计中的漏判。