### 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 非对称加密与签名知识体系 密钥生成 私钥 d(随机数) 公钥 P = d·G 数字签名 公钥加密 待签名消息 → 哈希 → 签名计算 (r,s) 明文 → 密钥派生 → C1 || C2 || C3 验签:使用公钥验证 (r,s) 解密:使用私钥提取明文 安全核心:私钥不可导出 / 随机数必须合格 / 签名不可重放 SM2 密文与签名使用 DER 编码封装 #### 数字签名与验签原理 **签名的核心思路** 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不匹配,验签必失败。