嗨,这里是肖恩!一个致力于把企业级的技术方案抽丝剥茧帮助你落地的程序员。
在当今的技术架构中,国产商用密码(简称「国密」)已不再是仅限于政务项目的冷门需求。随着等保2.0三级标准的深度普及,无论是在金融支付、核心系统数据脱敏,还是在移动端安全通信中,SM系列算法已经成为Java工程师必须掌握的核心技能。
掌握国密算法的落地,不仅是应对合规性检查,更是理解现代密码学工程实践的绝佳契机。本文将深入拆解SM2、SM3、SM4三大核心算法,并提供生产级项目的落地建议。
算法图谱:为什么是SM2/3/4?
在国际算法体系中,RSA、SHA256、AES是工程界的老三样。与之对应,国密算法体系提供了一套性能与安全性对标、甚至在特定场景下占优的替代方案。
- SM2:基于椭圆曲线(ECC)的非对称算法,是RSA算法的最佳替代方案。SM2 算法有 C1C3C2 和 C1C2C3 两种模式,不过一般我们都倾向于使用前者。
- SM3:消息摘要算法,输出256比特散列值,性能与安全性对标SHA-256。
- SM4:分组对称加密算法,分组长度和密钥长度均为128比特,是AES算法的强力对手。常见的加密模式有 ECB 模式和 CBC 模式,在后文中我们会详细讲解。
SM2:非对称加密与身份认证的基石
相较于RSA,SM2在相同的安全等级下,密钥长度更短(256位 vs RSA 2048位),这意味着更少的存储空间和更高的运算效率。
1. C1C2C3 与 C1C3C2 模式深度拆解
这是Java开发者最容易踩坑的地方。SM2的密文由三部分组成:
- C1:椭圆曲线上的随机点计算结果。
- C2:真正的密文数据。
- C3:摘要校验值,用于防止密文被篡改。
C1C3C2是当前国标(GB/T 32907)推荐的最优序列,旧版标准(C1C2C3)在早期的硬件加密机中较为多见。在前后端对接或跨语言调用(如Java与Go、C++对接)时,明确密文排序模式是联调成功的第一优先级。
2. 公钥压缩技术的权衡
SM2公钥由X和Y两个分量组成(通常为64字节)。为了节省带宽,可以采用压缩公钥格式(33字节)。在生产环境中,前端JS库和后端Java库对压缩格式的支持程度不一,统一使用非压缩格式(04前缀)是降低系统复杂性的权衡方案。
3. 签名与验签的并发处理
SM2签名操作涉及复杂的点运算,属于CPU密集型任务。在高并发的鉴权场景下,建议使用将相关对象池化或使用ThreadLocal缓存相关算法实例,避免频繁销毁和创建对象带来的GC压力。
SM3:数据完整性校验的防伪标签
SM3不仅是一个简单的哈希函数,它采用了迭代压缩结构,具有极高的碰撞抵御能力。
1. 消息填充与压缩
SM3处理流程是将消息填充至512比特的倍数,通过复杂的步函数运算生成256比特的摘要。它生成的指纹长度固定,不可逆。
2. 加盐哈希的最佳实践
在用户密码存储或敏感信息脱敏场景中,直接使用SM3(明文)极易受到彩虹表攻击。引入随机盐值(Salt)并进行迭代计算是生产环境的必选逻辑。通过SM3(明文 + 盐值),即使两个用户密码相同,存储的哈希值也会完全不同,极大提升了暴力破解的成本。
3. HMAC-SM3的应用
在开放平台的接口签名校验中,HMAC-SM3是确保消息真实性的首选方案。它结合了密钥与SM3算法,防止攻击者篡改请求参数,在政务系统对接中被广泛采用。
SM4:大规模数据加密的高速引擎
SM4算法在生产环境中承担了最繁重的数据加解密任务。其本质是将明文切分为固定长度的块进行迭代变换。在Java工程实践中,理解其工作模式比理解数学原理更重要。
1. ECB模式与CBC模式的抉择
ECB(电子密码本)模式将数据独立分块加密。这类模式的致命缺陷是相同的明文块会生成相同的密文块。在大型生产项目中,这可能导致模式泄露风险。CBC(密码分组链接)模式是这类场景的最佳实践,它通过引入初始向量(IV),使每一块的加密都依赖于前一块的执行结果。
2. 填充方案的标准化
由于SM4要求输入必须是128位的倍数,当末尾数据长度不足时,需要进行填充。PKCS7Padding是目前的通用标准。在Java中集成时,开发者必须严格对齐加解密双方的填充逻辑,否则会频繁的抛出BadPaddingException。
大型生产项目落地建议
在Java生态中实现国密算法,不建议从零编写数学公式,而是基于成熟的底层库进行封装。
1. 库的选择:Bouncy Castle (BC库)
Bouncy Castle是Java界的事实标准。它从1.57版本开始对国密提供了较好支持。在大型项目中,需要通过Security.addProvider(new BouncyCastleProvider())进行注册。也可以直接使用 hutool 封装好的国密相关工具类(SM2、SM3、SM4)。
2. 兼容性治理:双算法切换架构
在系统从RSA迁移到SM2的过程中,灰度切换策略是保障业务连续性的核心。建议抽象出一层CryptoService接口,通过配置中心动态控制算法开关。对于老用户数据使用RSA解密并SM2重新加密存储,实现无感迁移。
3. 密钥管理:硬件安全模块 (HSM)
在三级等保要求下,软件层面的密钥存储是不合规的。将SM2私钥托管在云厂商的硬件加密机(KMS)或物理服务器的加密卡中,通过调用硬件接口完成加解密,是金融级安全系统的标配。
4. 数据传输:国密双向SSL/TLS
在通信链路层,标准的HTTPS(TLS 1.2/1.3)主要使用国际算法。在特定的合规场景中,需要使用支持国密协议簇的Web服务器(如Tengine国密版)或定制化的Java SDK(如支持国密TLS协议栈的浏览器和客户端),确保全链路国密化。
前文从库选型、兼容性治理及硬件安全管理等维度,为大型项目的国密落地提供了方法论指导。为了进一步将理论转化为生产力,下文将通过一个具体的数据上报案例,拆解 SM2、SM3 与 SM4 算法在真实业务交互中是如何协同工作的。
实战示例
这里给出一个结合 SM2、SM3、SM4 的项目实战流程示例,其中涉及的算法有 CBC 模式的 SM4 对称加密,SM2withSM3 (即先将请求体用SM3做摘要,再用SM2做签名)签名 在这个示例中你可以看到一个涉及到国密加解密、签名验签过程的技术实践:
前期准备流程
sequenceDiagram
participant Reporter as 上报方
participant Platform as 平台
Note over Reporter, Platform: 阶段一:密钥初始化与分发
Platform->>Reporter: 分发平台 SM2 公钥
Note right of Platform: 平台持有: 平台 SM2 私钥
Note left of Reporter: 上报方持有: 平台 SM2 公钥 + 自身 SM2 私钥
Reporter->>Platform: 提供上报方 SM2 公钥
Note right of Platform: 平台存储: 应用ID + 上报方 SM2 公钥
Note over Reporter, Platform: 阶段二:SM4 对称密钥交换
Note left of Reporter: 生成 SM4 密钥
Reporter->>Platform: 提供 SM4 密钥
Note right of Platform: 加密保存上报方 SM4 密钥
Note over Reporter, Platform: 阶段三:交互准备
Rect rgb(240, 240, 240)
Note over Reporter, Platform: 双方约定签名原文格式与请求头格式
End
- 平台提供一个数据上报接口供上报方执行数据上报,所有数据传输必须加密+签名,平台内部持有平台 SM2 私钥,并将平台 SM2 公钥公布出来,所有上报方保存平台 SM2 公钥
- 上报方给平台提供上报方 SM2 公钥,并保存好自己的私钥,再给平台提供 SM4 密钥,双方分别对 SM4 密钥做加密保存
- 平台保存好上报方 SM2 公钥,并关联对应的应用 id,将上报方 SM2 公钥加密保存
- 双方约定好签名原文和请求头格式,准备交互
约定格式示例
签名原文:
应用id\n
请求方式\n
uri(含uri参数)\n // 这里的参数需要按照 a~z 的顺序排好
请求体SM4加密后的摘要\n
时间戳\n
随机数\n
签名原文为什么是这些字段?
因为签名原文的作用是标识这个请求一定是上报方发出的,且每一次请求都不可篡改,其他人不可伪造,基于这个大前提,我们来分析每个字段的作用:
- 应用id:标识接入方
- 请求方式+uri(含uri参数):标识请求的接口,uri参数需要按照 a~z 的顺序排好的原因,是服务端验签时也需要构造一样的签名原文来验签
- 请求体SM4加密后的摘要:上报方构建好请求体后,使用实时生成 SM4 IV,结合密钥对请求体进行加密。
- 时间戳:平台会校验请求时间戳与服务端的差异,如果相差时间太久就会拒绝本次请求。用于防重放攻击,即使有中间人截获了整个请求,在时间戳的校验下也只有一定时间内可以攻击成功。
- 随机数:一般形式为 uuid,上报方每次请求都生成不一样的,平台会缓存上报方每次请求的随机数,如果有请求的随机数产生了重复则拒绝本次请求,解决单时间戳校验的短时重放攻击问题。
请求头:
x-signature // 签名
x-app-id // 应用id
x-timestamp // 时间戳
x-nonce // 随机数
x-crypto-iv // 加密后的 SM4 IV
数据交互流程
这个流程,会有一点绕,结合着时序图和文字会更好理解哈~
sequenceDiagram
autonumber
participant Reporter as 上报方
participant Platform as 平台
Note over Reporter, Platform: 预置条件:双方交换公钥并存储己方私钥,预存基于 app-id 的 SM4 密钥
rect rgb(240, 248, 255)
Note over Reporter: 【数据准备与加密】
Reporter->>Reporter: 1. 生成实时 SM4 IV
Reporter->>Reporter: 2. 使用 SM4 密钥 + IV 加密原始数据 -> 密文
Reporter->>Reporter: 3. 生成 x-timestamp (时间戳) 和 x-nonce (随机数)
Reporter->>Reporter: 4. 准备 x-app-id
end
rect rgb(255, 245, 238)
Note over Reporter: 【加签与 IV 加密】
Reporter->>Reporter: 5. 组装签名原文 -> SM3 摘要 -> 上报方 SM2 私钥签名 -> x-signature
Reporter->>Reporter: 6. 使用 平台 SM2 公钥 加密 SM4 IV -> x-crypto-iv
end
Reporter->>Platform: 7. 发起 HTTP 请求 (携带密文 Body 及所有扩展 Header)
rect rgb(245, 255, 245)
Note over Platform: 【验签流程】
Platform->>Platform: 8. 接收请求,按约定构建签名原文并计算 SM3 摘要
Platform->>Platform: 9. 使用 上报方 SM2 公钥 验证 x-signature
end
alt 验签失败
Platform-->>Reporter: 10a. 返回对应报错提示
else 验签成功
rect rgb(255, 250, 205)
Note over Platform: 【解密流程】
Platform->>Platform: 10b. 使用 平台 SM2 私钥 解密 x-crypto-iv -> SM4 IV
Platform->>Platform: 11. 根据 x-app-id 检索预存的 SM4 密钥
Platform->>Platform: 12. 使用 SM4 密钥 + SM4 IV 解密密文 -> 原始数据
end
Platform->>Platform: 13. 数据处理并返回响应结果
end
- 上报方用密钥加密原始数据,得到密文
- 获取当前时间戳,生成随机数,写入
x-timestamp和x-nonce请求头 - 将应用id写入
x-app-id请求头 - 上报方按照约定构成签名原文,再对签名原文进行摘要后得到签名,写入
x-signature请求头 - 实时生成 SM4 IV,再用平台公钥加密 SM4 IV,写入
x-crypto-iv请求头 - 上报方构建好上述 http 请求后,发起请求,请求体
- 平台接收到请求,构建好签名原文后获取摘要,进行验签,验签有问题,就给出对应的报错提示
- 验签没问题,就用平台私钥解密
x-crypto-iv请求头中的数据拿到 SM4 IV,结合应用 id 拿到 SM4 密钥,即可对数据进行解密 - 解密完成后做数据处理,再将响应结果
一些 Q&A
1. 为什么要实时生成 IV?
可以想象下,如果没有 IV,或 IV 是固定的,在加密过程被中间人拿到了 SM4 密钥和 IV,那么中间人就可以解密历史和未来的所有数据。而动态的 IV,就是为了解决固定密钥一旦泄露,会导致所有数据都有安全风险的问题。
2. 签名原文为什么是这些字段?
签名原文的作用是保证这一次请求,没有其他任何的方法可以伪造,因此就要考虑以下几个点:
-
是不是某个上报方调用的?
-
调用的接口和参数是不是这一个?会不会被抓包后请求别的接口?
-
会不会被抓包后重放?
而签名原文中的每个字段,则分别解决了这些问题:
-
应用id:解决「我」是「我」的问题
-
请求方式 + uri + uri参数 + 请求体摘要:解决了调用的接口和参数的唯一性问题
-
时间戳 + 随机数:解决了抓包后,一定时间内被重放的问题。随机数在平台服务端,一般是使用 Redis 等缓存来存储,存储的有效期刚好就是请求时间戳与服务器时间戳允许的时间差,这样就可以保证,一个请求即使被抓包,也无法被重新调用
3. 为什么 SM2 加密是用公钥,而签名是用私钥?
因为私钥是属于系统中的「最高机密」,绝对不允许外泄,而公钥是可以公布在一定的网络范围甚至是公网的。
加密后的数据要保证只有特定的人可以解密,而签名要保证只有特定的人可以签名,因此 SM2 是公钥加密、私钥解密,私钥签名、公钥验签。
小尾巴
嗨,这里是肖恩!
其实上面的流程,还有一个可以补充的步骤,能够让上报方也能够验证平台的可信,知道的小伙伴也可以在评论区补充!