在对接政务、金融或各类企业级开放平台时,很多开发者第一次接触到"国密算法"(国家商用密码标准)。
接口文档通常会在开头写下这么一句要求:"为了保障传输安全,本网关所有请求均采用 SM2 加密与 SM3 签名。"
然而在联调阶段,控制台里最频繁弹出的往往是这两句让人抓狂的报错:
- "验签失败(Signature verification failed)":正文字段明明一个都没改,为什么对不上?
- "解密失败(Decryption error)":公私钥明明是从后台复制出来的,为什么解出来是一堆乱码甚至直接抛出异常?
问题的根源,往往在于很多同学把"签名"和"加密"当成了一回事,导致概念混淆、调用顺序颠倒、公私钥拿错。
其实,同一次请求里包裹着两道性质截然不同的手续:
- SM2 解决的是 「别人看不懂」(防窃听);
- SM3 解决的是 「别人改不了」(防篡改)。
本文将循序渐进地拆解这两道锁的工作原理、协作机制与实战排坑要点,带你一次性搞懂国密 SM2 与 SM3。
1. 心智模型:寄一份重要快递
要彻底理清它们的分工,不妨把前端发往网关的一次接口请求,想象成寄送一份装有重要合同的快递:
text
┌─────────────────────────────────────────────────────────────────┐
│ 📄 业务正文(如:身份证、手机号、银行卡号) │
└────────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 🔒 第一道锁:SM2 =「装进密码保险箱」(防窃听,仅特定敏感接口需要) │
│ • 用平台给的【公钥】把明文锁进箱子,变成一串乱码密文 │
│ • 路上抓包的黑客即使截获快递,没有【私钥】也打不开、看不懂 │
└────────────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 🏷️ 第二道锁:SM3 =「贴上防伪封条」(防篡改,所有网关请求必选) │
│ • 把箱子外面的快递单(含应用ID、时间戳、保险箱密文)排好 │
│ • 用 SM3 算出唯一的 64 位防伪指纹(sign),贴在包裹封口处 │
│ • 哪怕路上有人偷偷改动了一个标点符号,网关重算指纹就会对不上,直接拒收 │
└─────────────────────────────────────────────────────────────────┘
把两道锁的关键维度横向对比:
| 维度 | SM3 签名(防伪封条) | SM2 加密(密码保险箱) |
|---|---|---|
| 对标国际标准 | SHA-256、MD5(哈希摘要 / 散列算法) | RSA、ECC(非对称公私钥加密) |
| 能否逆向还原原文 | 绝对不能(单向指纹,不可逆) | 可以 (公钥加密 → 私钥解密) |
| 核心防御目标 | 防篡改:确保数据在传输途中未被微调 | 防窃听:保护敏感个人隐私与商业数据 |
| 每次请求都要算吗 | 必须算(只要请求走网关就必须附带) | 按需开启(仅敏感接口或后台勾选了加密时才开启) |
| 是否需要配置密钥 | 无需密钥(纯数学哈希,公开算法) | 必须配对密钥(公钥加密,私钥解密) |
搞清楚了这个宏观心智模型后,我们逐层深入剖析这两道锁的技术细节。
2. 深入第一道锁:SM3「防伪封条」
2.1 什么是 SM3?
SM3 是中国国家密码管理局发布的密码杂凑算法(商用密码 GM/T 0004)。它的角色就相当于国际密码学体系中的 SHA-256。
SM3 拥有三个核心特性:
- 单向不可逆:你可以轻易把一段千万字的长文通过 SM3 压成一段 64 位十六进制字符串,但世界上没有人能根据这 64 位字符串反推出原文。
- 固定输出长度 :不论输入是空的
""、几个单词,还是几个 G 的大文件,SM3 计算后的输出长度恒定为 256 bit(即 32 字节,转为十六进制就是 64 个字符)。 - 极敏感的雪崩效应(Avalanche Effect):原文只要改变了仅仅 1 个比特(比如一个逗号变句号),算出来的 64 位哈希值就会发生天翻地覆的变化,毫无规律可循。
2.2 网关是如何利用 SM3 验签的?
在开放平台中,客户端与网关之间约定了一套标准的验签流程:
正是因为雪崩效应的存在,黑客中途哪怕只篡改了请求里的某一个参数值,网关收到后重新用 SM3 算出来的指纹就必然和客户端传上来的 sign 不相等,篡改行为立刻暴露无遗。
2.3 SM3 签名的 3 大高频踩坑点
在编写签名代码时,以下几个细节只要稍有疏忽,就会导致网关"验签失败":
- 字典序排序(ASCII 码从小到大) : 参与签名的参数名必须按照 ASCII 字典顺序排序。例如:
app_id排在biz_content前面,biz_content排在charset前面。 - 空值过滤规则 : 在绝大多数网关规范中,值为
null、undefined或空字符串""的参数不参与签名拼接 。如果客户端拼接了version=&...,而网关在计算时剔除了version,两者拼接出的字符串不同,验签必然失败。 - 透传字段与 Header 不要盲目塞进签名串 : 许多接口请求会包含 HTTP 请求头(如
Authorization、certify_token)或者外部透传字段(如header={"userid":"..."})。除非接口文档特别注明,否则这些传输层字段通常不属于签名字段集合,切忌画蛇添足。
3. 深入第二道锁:SM2「密码保险箱」
3.1 什么是 SM2?
SM2 是基于**椭圆曲线密码学(ECC)**的非对称密码算法(商用密码 GM/T 0003),在国际上主要对标 RSA。
相比于传统的 RSA-2048,SM2(256 位椭圆曲线)在提供同等甚至更高安全强度的前提下,密钥长度更短、计算速度更快、网络传输的密文体积也更小。
非对称加密的核心在于成对的密钥:
- 公钥(Public Key) :用于加密(锁门)。公钥是公开的,任何人拿到都可以把明文数据锁进密文保险箱。
- 私钥(Private Key) :用于解密(开门)。私钥必须严格保密,只有掌握私钥的人才能打开保险箱、还原明文。
3.2 疑惑解答:为什么前端配置里既有公钥也有私钥?
刚入行的同学常被一个"安全常识"困扰:"私钥不是绝对不能暴露给前端/客户端吗?为什么网关接入文档要求前端同时配置公钥和私钥?"
这是因为在很多轻量级政务与企业网关场景中,执行的是双向加密通信:

- 发出去时(客户端 → 网关) :客户端使用平台下发的公钥把敏感正文锁上。此时密文在网络传输中就算被抓包,中间人没有平台的私钥,什么也看不见;
- 收回来时(网关 → 客户端) :网关用应用的公钥 把响应数据锁上,客户端收到后再用本地配置的应用私钥解开。
两对公私钥各司其职,保证了整个传输回路的双向防窃听。
3.3 为什么 SM2 密文大多以 04 开头?
仔细观察 SM2 加密后的十六进制字符串,你会发现几乎所有密文都以 04 开头:
text
04c852914e34a6bc8910... (后面是一长串十六进制字符串)
这并不是巧合,而是密码学规范中**未压缩椭圆曲线点(Uncompressed Point Format)**的标准标识:
- 椭圆曲线上的一个公钥点由坐标 (x,y) 组成;
- 国际及国密规范定义:以单字节十六进制
04作为未压缩点的前缀,表示其后紧跟着 x 坐标和 y 坐标。
实战踩坑:
- 某些前端加解密库(如
sm-crypto)加密输出的十六进制可能默认不带04,必须根据网关要求手动补充; - 解密时,如果密文以
04开头,需要先将前两个字符(04)剥离后再送给底层解密函数,否则解密器可能会抛出point not on curve(点不在曲线上)或直接解密失败。
3.4 模式之坑:C1C3C2 还是 C1C2C3?
SM2 密文由三部分构成:
- C1:随机产生的椭圆曲线点;
- C2:真正的密文数据;
- C3:针对明文和曲线点计算出的摘要校验码(防篡改)。
历史上出现过两种排列模式:
- 老国标排列:
C1C2C3 - 新国标与主流规范:
C1C3C2
目前主流政务和金融网关几乎统一采用 C1C3C2 (例如在 sm-crypto 中传入 cipherMode 为 1)。如果网关是 C1C3C2,而你的客户端配置成了 C1C2C3,哪怕两边的公私钥完全配对,也会直接导致解密失败!
4. 终极合体:两道锁的「穿衣顺序」
当一个请求既要开启 SM2 加密,又要走 SM3 签名时,顺序只有一个,绝不能颠倒!
铁律:先锁保险箱,再贴封条!
为什么反过来做必死无疑?
假设你犯了逻辑错误:先用明文算好签名,再去加密正文。
- 客户端计算签名时,用的是明文字符串
biz_content={"phone":"138..."}; - 算完签名后,你把
biz_content加密成了04a1b2...; - 网关收到请求时,只能看到密文
04a1b2...,网关拿密文重新计算 SM3; - 结果 :密文算出来的哈希必然不可能等于明文算出来的签名,网关在第一步验签时就会直接将其当成"非法篡改"予以拦截,根本不会走到解密步骤!
5. 联调排错速查手册(Checklist)
下次联调若遇到报错,请对照这份检查清单逐项排查:
🛠️ 当遇到「验签失败(SM3)」
- 顺序对了吗? 参与签名的
biz_content是否是 SM2 加密后的密文,而不是明文? - 参数排序对了吗? 是否严格按照 ASCII 字典升序(从小到大)排列?
- 空值是否已过滤?
null、undefined、""是否已被排除在待签名字符串之外? - 是否误加入了额外字段? 检查 HTTP Headers、Cookie、透传 Token 是否被错误塞进了签名列表?
- 字符串拼接格式对不对? 是否存在多余的空格、换行符或多余的
&符号?编码是否统一为 UTF-8?
🔓 当遇到「解密失败(SM2)」
-
04前缀处理对了吗? 检查解密前是否需要剥离04,或者加密后是否漏掉了04? - 排列模式是否一致? 确认双方是否都统一采用了
C1C3C2(新国标推荐)? - 公私钥是否配对? 确认是否将"平台公钥"和"应用自建公钥"混淆?私钥是否写错?
- 返回数据真是密文吗? 某些网关在报错(如 404/500/Token 过期)时,返回的其实是明文错误 JSON(例如
{"code":500,"msg":"系统异常"}),若直接将其当作密文送入 SM2 解密函数,必然报语法错误。
6. 总结
搞懂国密 SM2 与 SM3 的本质,其实就这一张图与一段十六字口诀:
text
┌──────────────────────┐
│ 业务请求 │
└──────────┬───────────┘
│
【第一道锁】 ▼
SM2 加密(可逆) [ 04... 密文保险箱 ] ---> 解决「别人看不懂」
│
【第二道锁】 ▼
SM3 签名(不可逆) [ 64 位防伪封条 sign ] ---> 解决「别人改不了」
联调十六字口诀:
公钥锁箱,密文贴标;先密后签,收回私开。
分清职责、理顺先后,国密网关对接其实一点也不神秘。