导读:本期聚焦于风铃创作的《Spring Boot如何整合国密算法SM2、SM3、SM4实现数据加密传输?》,敬请观看详情。数据安全合规要求越来越严格,国密算法正在成为国内金融、政务系统的标配。本文围绕Spring Boot项目落地SM2、SM3、SM4三类国密算法展开,先讲清楚三种算法的分工:SM2负责非对称加密与签名,SM3用于摘要计算,SM4承担对称加解密。文章基于Bouncy Castle和hutool-crypto给出完整代码示例,包括依赖引入、密钥生成、工具类封装、接口参数加解密实战,以及混合加密方案的设计思路,帮你快速在现有项目中接入国密体系。

金融、政务类的系统对数据安全要求越来越高,等保测评和密评也逐渐把国密算法列成了硬性指标。SM2、SM3、SM4是国家密码管理局发布的商用密码标准,分别在非对称加密、摘要、对称加密三个领域对标RSA、SHA-256和AES。这篇文章就来聊聊怎么在一个已有的Spring Boot项目里把这些算法落地,包括依赖配置、工具类封装、接口层加解密实战,以及混合加密的方案设计。

Spring Boot如何整合国密算法SM2、SM3、SM4实现数据加密传输?

一、先弄清楚三种算法各自的分工

很多刚接触国密的开发者容易把SM2、SM3、SM4混为一谈,实际上三者定位完全不同,选错了算法类型,安全方案就站不住脚。

SM2是基于椭圆曲线(ECC)的非对称加密算法,可以理解为国密版的RSA,但密钥更短、速度更快。256位的SM2密钥安全强度约等于3072位的RSA密钥。它主要用在两个场景:一是公钥加密、私钥解密,适合传输短小敏感数据;二是数字签名,用于验证数据完整性和身份。

SM3是密码散列算法,对标MD5和SHA-256,输出固定256位的摘要,常用于密码存储、数据完整性校验和签名前的原文摘要计算。它本身不可逆,也没有密钥的概念。

SM4是分组对称加密算法,对标AES,密钥长度128位,分组长度也是128位。它适合对大批量数据做加解密,性能上和AES接近。典型的用法是SM4加密报文体,SM2加密SM4的密钥,两者配合构成混合加密。

二、引入依赖并封装工具类

Java原生JDK并不支持国密算法,需要借助Bouncy Castle作为安全提供者。这里推荐直接使用hutool-crypto,它对Bouncy Castle做了一层封装,API非常简洁,同时也支持BC库的底层调用方式。

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.25</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk18on</artifactId>
    <version>1.77</version>
</dependency>

注意Bouncy Castle的版本要和JDK版本匹配,JDK8用bcprov-jdk15on,JDK17及以上用bcprov-jdk18on,否则注册Provider时会抛出异常。

接着封装一个国密工具类,把SM2、SM3、SM4的常用操作都收拢进来,业务代码只面向这个门面类编程,后面更换底层实现也不影响业务层:

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.symmetric.SM4;

public class SmCryptoUtil {

    // SM4加密,key为16字节hex字符串或字节数组
    public static String sm4Encrypt(String plainText, byte[] key) {
        SM4 sm4 = new SM4(key);
        return sm4.encryptHex(plainText);
    }

    public static String sm4Decrypt(String cipherHex, byte[] key) {
        SM4 sm4 = new SM4(key);
        return sm4.decryptStr(cipherHex);
    }

    // SM2公钥加密,前端持公钥加密,后端私钥解密
    public static String sm2Encrypt(String plainText, String publicKey) {
        SM2 sm2 = SmUtil.sm2(publicKey, null);
        return sm2.encryptHex(plainText, KeyType.PublicKey);
    }

    public static String sm2Decrypt(String cipherHex, String privateKey) {
        SM2 sm2 = SmUtil.sm2(null, privateKey);
        return sm2.decryptStr(cipherHex, KeyType.PrivateKey);
    }

    // SM3摘要,常用于密码散列
    public static String sm3Digest(String plainText) {
        return SmUtil.sm3(plainText);
    }

    // SM2签名与验签
    public static String sm2Sign(String data, String privateKey) {
        SM2 sm2 = SmUtil.sm2(null, privateKey);
        return sm2.signHex(data);
    }

    public static boolean sm2Verify(String data, String sign, String publicKey) {
        SM2 sm2 = SmUtil.sm2(publicKey, null);
        return sm2.verifyHex(data, sign);
    }
}

密钥管理上有个细节要注意:SM4的密钥必须是16字节,SM2的密钥对一般用SmUtil.sm2()生成一次后固化到配置中心,不要每次请求都重新生成,否则解密方拿不到对应密钥。密钥建议通过环境变量或配置中心注入,严禁硬编码在代码里,这一点在密评时是重点检查项。

三、在接口层落地加解密

工具类封装好之后,接下来解决接口层面的统一加解密。最优雅的方式是自定义注解加RequestBodyAdvice和ResponseBodyAdvice,在参数进入Controller之前完成解密,在响应返回之前完成加密,业务代码完全无感。

// 1. 自定义注解,标记需要加解密的接口
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Encrypt {
}

// 2. 请求解密:前端用SM4加密请求体,密钥通过SM2保护传输
@ControllerAdvice
public class DecryptRequestAdvice implements RequestBodyAdvice {

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage input, MethodParameter parameter,
            Type targetType, Class converterType) throws IOException {
        String body = IoUtil.read(input.getBody(), StandardCharsets.UTF_8);
        // 解析出加密的业务数据和加密后的SM4密钥
        JSONObject json = JSONUtil.parseObj(body);
        String sm4KeyHex = SmCryptoUtil.sm2Decrypt(
                json.getStr("key"), SecurityKeys.SM2_PRIVATE_KEY);
        String plain = SmCryptoUtil.sm4Decrypt(
                json.getStr("data"), HexUtil.decodeHex(sm4KeyHex));
        InputStream is = new ByteArrayInputStream(
                plain.getBytes(StandardCharsets.UTF_8));
        return new HttpInputMessage() {
            @Override public InputStream getBody() { return is; }
            @Override public HttpHeaders getHeaders() { return input.getHeaders(); }
        };
    }

    @Override public boolean supports(MethodParameter m, Type t, Class<?> c) {
        return m.hasMethodAnnotation(Encrypt.class)
                || m.getContainingClass().isAnnotationPresent(Encrypt.class);
    }
    // 其他方法省略
}

前端侧需要一个对应的加密流程:先用后端下发的SM2公钥加密一个随机生成的SM4密钥,再用这个SM4密钥加密真正的业务报文,把两者拼成JSON提交。这就是典型的信封加密(混合加密)思路:非对称算法解决密钥分发问题,对称算法负责大数据量的加解密,兼顾安全性和性能。

登录密码这类场景则不需要加密,用SM3摘要加盐即可。可以在用户表存sm3(salt + password),盐值每个用户独立生成,校验时重新计算摘要比对。相比SM2加密存储,摘要方案不可逆且无需管理解密密钥,更适合密码这种只需要验证、不需要还原的数据。

四、几个容易踩的坑

第一个坑是SM2密文格式不兼容。国密标准C1C2C3和新的C1C3C2两种排列顺序都有实现,Java端hutool默认输出C1C3C2,而部分前端js库(如sm-crypto)默认是C1C2C3,联调时解密失败大概率是这个问题。可以在创建SM2对象时指定SM2Engine.Mode.C1C2C3来对齐。

第二个坑是密钥hex字符串直接当字节用。前端传来的密钥如果是64位hex字符串,实际只有32字节,直接getBytes()当密钥会报错,必须先HexUtil.decodeHex()转成字节数组,这也是联调阶段最常见的异常来源。

第三个坑是SM4的ECB模式。默认的ECB模式对相同明文块产生相同密文,容易被分组重放攻击。建议显式指定CBC或GCM模式,并配合随机IV,每次加密生成新的初始向量随密文一起传输。安全要求高的系统,密评时ECB模式基本过不了关。

整体来看,Spring Boot接入国密算法的门槛并不高,核心工作在于选对算法组合、封装好工具层、处理好前后端密文格式对齐这三件事。把混合加密方案和注解式的统一加解密搭好之后,后续新增接口只需要打一个注解就能享受国密保护,维护成本非常低。

Spring Boot国密算法SM4加密修改时间:2026-09-05 23:38:51

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260905/51218.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。