### SM2 非对称加密与签名算法详解
#### 椭圆曲线与SM2的整体设计
SM2是国家密码管理局公布的椭圆曲线公钥密码算法标准。它在设计上对标了国际上的ECDSA和ECIES,但你想想看,它又不是简单的翻版。我个人习惯把SM2看作一套“国产定制”的密码工具箱——密钥生成、数字签名、密钥交换、公钥加密,它都占了。
我在项目中见过不少人对SM2的椭圆曲线参数产生疑问。其实SM2选用的是一条256位的素数域椭圆曲线,安全性相当于RSA 3072位。为什么选256?因为同样是256位,椭圆曲线能提供更小的密钥尺寸和更快的计算速度。
| 对比项 | RSA-2048 | SM2 (256位) |
|--------|----------|-------------|
| 密钥长度 | 2048位 | 256位 |
| 签名速度 | 较慢 | 较快 |
| 证书大小 | 大 | 小 |
| 兼容性 | 全球通用 | 国密标准 |
#### 密钥生成与椭圆曲线参数结构
**核心参数**
SM2的椭圆曲线由一组参数定义:\( (p, a, b, G, n, h) \)。这里面p是素数,a和b决定曲线方程 \( y^2 = x^3 + ax + b \),G是基点,n是基点阶,h是余因子。
在实际项目中,这些参数都是固定的,由国家密码管理局指定。嗯,这里要注意:千万不能私自修改这些参数,否则算法就不再是SM2了。
**密钥生成过程**
实际生成密钥时,私钥是一个随机数 \( d \),取值范围是 \( [1, n-2] \)。公钥则是 \( P = d \cdot G \),也就是私钥乘以基点。
我记得有次开发同事自己写了一个随机数生成器来产生私钥,结果因为随机数质量不够好,导致私钥存在可预测性。**这是不行的**。SM2的私钥必须使用国家密码管理局认可的随机数发生器。
**这里画一个流程图,帮助你理解密钥生成与使用的关系**:
#### 数字签名与验签原理
**签名的核心思路**
SM2的签名,说白了就是签名者用私钥对消息的哈希值进行运算,生成一对大整数 (r, s)。验证方拿到公钥 (r, s) 和原消息后,通过计算可以确认签名是否属于你。
为什么这样设计?因为要满足国密标准中的安全要求。**不能伪造、不能抵赖、签名本身也不能泄露私钥信息**。
具体的步骤我简单列一下:
1. 对消息计算哈希值,得到 \( e \)
2. 生成随机数 \( k \),计算椭圆曲线点 \( (x_1, y_1) = k \cdot G \)
3. 计算 \( r = (e + x_1) \mod n \),如果 \( r = 0 \) 或 \( r + k = n \),重新生成 \( k \)
4. 计算 \( s = ((1 + d)^{-1} \cdot (k - r \cdot d)) \mod n \),如果 \( s = 0 \),重新生成 \( k \)
5. 输出签名 \( (r, s) \)
**验签步骤**
验签时,验证方需要使用签名者的公钥。过程如下:
1. 同样计算消息的哈希值 \( e \)
2. 计算 \( t = (r + s) \mod n \),如果 \( t = 0 \),验证失败
3. 计算椭圆曲线点 \( (x_1, y_1) = s \cdot G + t \cdot P \)
4. 验证 \( r = (e + x_1) \mod n \)
我一直和团队强调:**验签时一定要验证 \( r \) 和 \( s \) 的范围**。我曾经在项目中遇到过一个诡异的问题——对方提供的签名中 \( r \) 或 \( s \) 值超出了 \( [1, n-1] \) 的范围,但验签库没有做范围检查,导致异常通过了验签。后来才发现,这是某些测试工具的不规范实现导致的。
避坑指南:我曾见过一个客户将 SM2 签名中的 (r, s) 直接拼接后传输,没有使用标准的 DER 编码。接收方在解析时无法区分 r 和 s 的边界。国密标准明确要求签名输出必须使用 DER 编码格式,所以一定要按标准来。
#### SM2 加密与解密解析
**加密过程**
SM2的加密,是基于公钥的。假设你有别人公钥,想给他发一条密文,流程是这样的:
1. 生成随机数 \( k \),计算椭圆曲线点 \( C_1 = k \cdot G \)
2. 计算椭圆曲线点 \( S = h \cdot P \),如果 \( S \) 是无穷远点,报错
3. 计算 \( k \cdot P = (x_2, y_2) \),利用 KDF 从 \( x_2 \) 派生密钥 \( t \)
4. 用 \( t \) 加密明文 \( M \),得到 \( C_2 = M \oplus t \)
5. 计算哈希 \( C_3 = Hash(x_2 \| M \| y_2) \)
6. 输出密文 \( C_1 \| C_2 \| C_3 \)
**解密过程**
持有私钥的人,解密过程刚好反过来:
1. 从密文中提取 \( C_1 \),验证它是椭圆曲线上的点
2. 计算 \( d \cdot C_1 = (x_2, y_2) \),用同样的 KDF 派生密钥 \( t \)
3. 计算 \( M = C_2 \oplus t \),还原明文
4. 验证 \( C_3 \) 是否等于 \( Hash(x_2 \| M \| y_2) \)
我在第一线做研发时,发现不少开发者会在解密后忘记验证 \( C_3 \),这会导致密文被篡改的风险。说白了,SM2加密自带完整性校验,你不做校验就等于放弃了数据完整性。
工程提示:SM2 的密文结构是 C1 || C2 || C3,C1 是椭圆曲线点,长度较长。在实际传输时,建议将 C1 压缩后再发送,可以节省约一半的带宽。SM2 支持点压缩模式。
#### 算法对比与应用场景
| 算法功能 | 密钥类型 | 典型场景 | 常见问题 |
|----------|----------|----------|----------|
| 密钥生成 | 无 | 初始化阶段 | 随机数质量 |
| 签名与验签 | 私钥签名,公钥验签 | 身份认证、电子合同 | 随机数重复 |
| 密钥交换 | 双方私钥/公钥 | 安全信道建立 | 协议一致性 |
| 公钥加密 | 公钥加密,私钥解密 | 传输密钥加密 | C1点校验 |
我记得在参与一个政务系统时,开发同事直接把RSA的加解密流程套用到SM2上,结果发现加解密效率差异巨大。**其实SM2更适合做小型数据的加密**,比如传输对称密钥,而不是直接加密大文件。
#### 实施建议
- 确保底层使用的是国家密码管理局认可的SM2椭圆曲线参数
- 随机数生成器必须符合GB/T 32918.5中的要求
- 签名输出必须采用DER编码
- 加密操作必须包含完整性校验(C3验证)
- 密钥组件在使用前做点存在性验证
实战要点:我在多个合规项目中踩过一个坑——SM2签名时使用的用户ID(ZA)必须与验签时一致。标准规定ZA = Hash(ENTL || ID || a || b || Gx || Gy || Px || Py),如果ID不匹配,验签必失败。