SM2
概述
SM2 是中国国家密码管理局发布的椭圆曲线公钥密码算法标准(GB/T 32918-2016),属于国密算法三件套(SM2/SM3/SM4)中的公钥基础设施部分。256 bit 密钥长度提供约 RSA 3072 bit 的安全强度,在政府、 金融、支付等领域为合规强制的密码方案。
基础
算法定位
基于椭圆曲线密码学(Elliptic Curve Cryptography, ECC)的公钥算法。SM2 选用的曲线方程为 y² = x³ + ax + b,其参数(a, b, p, n, G)由国家密码管理局固定指定,
而非像 secp256k1 那样由国际社区标准化。
核心功能
标准定义三种密码学原语:
- 数字签名:身份认证、消息完整性校验、不可否认性
- 密钥交换:安全协商会话密钥(如 TLS 握手中的
TLCP国密协议) - 公钥加密:用接收方公钥加密数据,私钥解密
与 RSA 的对比
| 维度 | SM2 (ECC) | RSA |
|---|---|---|
| 密钥长度 | 更短(256 bit) | 更长(3072 bit 对等强度) |
| 计算性能 | 签名/验签更快 | 相对较慢 |
| 存储/传输开销 | 更小 | 更大 |
| 专利/生态 | 中国自主标准,政府/金融行业强制要求 | 国际通用标准 |
工程陷阱
签名格式的 DER 与裸字节混用
不同库的默认输出格式截然不同
SM2 签名结果并非天然就是 64 字节的 r||s 裸字节。Java BouncyCastle 的国密扩展、GmSSL 的 Java/Go 实现默认输出 ASN.1 DER 编码——一段包含两个 INTEGER 的 SEQUENCE,
长度通常 70-72 字节(因大数首字节补零而浮动);而 Node.js 的 sm-crypto、Python 的 gmssl、前端 JS 库则默认输出裸 64 字节 r||s。两者混用时验签失败,
错误信息通常是"签名无效"而非"格式错误",调试方向直接跑偏。工程上必须在接口文档中显式约定签名值的序列化格式,并在验签前做格式检测与转换,绝不能假设"都是 SM2 就能互通"。
Z 值哈希中的隐形默认 ID
默认用户标识参与签名但不可见
SM2 签名不是简单的 H(M),而是 H(Z_A || M)。Z_A 是将用户标识 ID、椭圆曲线参数和公钥坐标一起 SM3 哈希得到的绑定值。
国标 GB/T 35276-2017 规定的默认用户标识是字符串 "1234567812345678"(16 字节 ASCII),但多数开发者根本不知道这个默认值的存在,更不知道它参与了签名哈希。这意味着同一消息、同一私钥,
只要 ID 不同,签名结果就完全不同。工程上如果签名方使用默认 ID,而验签方(如另一个微服务或硬件加密机)被配置为自定义 ID,验签会毫无悬念地失败——而且检查私钥、消息、随机数都看不出问题,因为问题出在隐形的 Z 值计算上。
密文排列的新旧标准之争
C1C3C2 与 C1C2C3 顺序差异
SM2 加密结果的排列在现行国标 GB/T 32918.4-2016 中是 C1 || C3 || C2(C1 是 65 字节曲线点,C3 是 32 字节 SM3 摘要,C2 是变长异或密文), 但早期草案 GM/T 0003.4-2012 和部分国产硬件加密机、UKey 固件实现的是 C1 || C2 || C3。如果你的系统里同时存在"新标准库"和"旧硬件设备",加解密对接时会出现"自己能解、对方不能解"的诡异现象。更麻烦的是, 有些国产中间件为了兼容同时支持两种顺序,但默认配置不同,导致问题只在跨系统联调时才暴露。工程上必须在集成测试阶段就明文约定密文排列顺序,并在代码中做显式校验而非依赖库的默认行为。