PostsMapsLinks
Security

SM2

国密 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。如果你的系统里同时存在"新标准库"和"旧硬件设备",加解密对接时会出现"自己能解、对方不能解"的诡异现象。更麻烦的是, 有些国产中间件为了兼容同时支持两种顺序,但默认配置不同,导致问题只在跨系统联调时才暴露。工程上必须在集成测试阶段就明文约定密文排列顺序,并在代码中做显式校验而非依赖库的默认行为。

Copyright © 2024 Lionad - CC-BY-NC-CD-4.0