在金融、政务、物联网等对数据安全要求极高的场景中,国密SM4对称加密算法已成为替代DES、AES的主流选择。SM4是我国自主研发的分组密码算法,分组长度和密钥长度均为128比特,安全性和效率均衡,且符合国家密码管理局的安全规范。
本文将带你从零开始,在SpringBoot项目中无缝集成SM4算法,实现通用工具类、接口加解密、配置敏感信息加密,覆盖企业级开发的核心实战场景。
一、前置知识
- SM4核心概念
- 对称加密:加密和解密使用同一把密钥,密钥必须妥善保管;
- 分组密码:将明文按128位分组加密,支持ECB、CBC两种工作模式(CBC模式更安全,推荐使用);
- 填充方式:PKCS7Padding(国密标准填充)。
- 开发环境
- JDK 8+
- SpringBoot 2.x/3.x
- Maven/Gradle
二、核心依赖引入
SpringBoot本身不内置国密算法,我们使用BouncyCastle(Java主流密码学库,完美支持国密)实现SM4加解密。
1. Maven依赖(推荐)
在pom.xml中添加依赖:
<!-- BouncyCastle 国密算法支持 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
2. Gradle依赖
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
补充:如果项目中已引入
hutool工具包,也可以直接使用Hutool封装的SM4工具类,底层同样依赖BouncyCastle。
三、SM4加解密工具类(通用版)
这是整个集成的核心,我们封装CBC模式(带向量) 和ECB模式两种实现,优先使用CBC模式(安全性更高,需要额外的偏移量IV)。
工具类特性:
- 支持16进制/Base64编码输出(兼容接口传输)
- 自动补全密钥/IV长度(SM4要求密钥和IV必须为16字节)
- 全局异常处理,开箱即用
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
import java.util.Base64;
/**
* 国密SM4加解密工具类
* 支持 CBC/ECB 模式,推荐 CBC模式(带IV向量)
*/
public class Sm4Util {
// 注册BouncyCastle加密提供者
static {
Security.addProvider(new BouncyCastleProvider());
}
// SM4算法名称
private static final String SM4_ALGORITHM = "SM4";
// CBC模式:SM4/CBC/PKCS7Padding(推荐,安全性高)
public static final String SM4_CBC_PADDING = "SM4/CBC/PKCS7Padding";
// ECB模式:SM4/ECB/PKCS7Padding(无需IV,简单但安全性较低)
public static final String SM4_ECB_PADDING = "SM4/ECB/PKCS7Padding";
// SM4 密钥长度固定 128位(16字节)
private static final int SM4_KEY_LENGTH = 16;
// CBC模式IV向量长度固定 16字节
private static final int SM4_IV_LENGTH = 16;
// ==================== 1. 生成SM4密钥/IV向量 ====================
/**
* 生成SM4随机密钥(16字节)
*/
public static String generateSm4Key() throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(SM4_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
kg.init(128);
SecretKey secretKey = kg.generateKey();
return Hex.toHexString(secretKey.getEncoded());
}
/**
* 生成CBC模式随机IV向量(16字节)
*/
public static String generateSm4Iv() {
byte[] iv = new byte[SM4_IV_LENGTH];
new java.security.SecureRandom().nextBytes(iv);
return Hex.toHexString(iv);
}
// ==================== 2. CBC模式加解密(推荐) ====================
/**
* SM4-CBC加密
* @param plainText 明文
* @param hexKey 16字节密钥(16进制字符串)
* @param hexIv 16字节IV向量(16进制字符串)
* @return 16进制密文
*/
public static String encryptCbc(String plainText, String hexKey, String hexIv) throws Exception {
Cipher cipher = Cipher.getInstance(SM4_CBC_PADDING, BouncyCastleProvider.PROVIDER_NAME);
SecretKeySpec secretKeySpec = new SecretKeySpec(Hex.decode(hexKey), SM4_ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(Hex.decode(hexIv));
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivSpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
return Hex.toHexString(encrypted);
}
/**
* SM4-CBC解密
* @param cipherText 16进制密文
* @param hexKey 16字节密钥(16进制字符串)
* @param hexIv 16字节IV向量(16进制字符串)
* @return 明文
*/
public static String decryptCbc(String cipherText, String hexKey, String hexIv) throws Exception {
Cipher cipher = Cipher.getInstance(SM4_CBC_PADDING, BouncyCastleProvider.PROVIDER_NAME);
SecretKeySpec secretKeySpec = new SecretKeySpec(Hex.decode(hexKey), SM4_ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(Hex.decode(hexIv));
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);
byte[] decrypted = cipher.doFinal(Hex.decode(cipherText));
return new String(decrypted, "UTF-8");
}
// ==================== 3. ECB模式加解密(简易版) ====================
public static String encryptEcb(String plainText, String hexKey) throws Exception {
Cipher cipher = Cipher.getInstance(SM4_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME);
SecretKeySpec secretKeySpec = new SecretKeySpec(Hex.decode(hexKey), SM4_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
return Hex.toHexString(encrypted);
}
public static String decryptEcb(String cipherText, String hexKey) throws Exception {
Cipher cipher = Cipher.getInstance(SM4_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME);
SecretKeySpec secretKeySpec = new SecretKeySpec(Hex.decode(hexKey), SM4_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] decrypted = cipher.doFinal(Hex.decode(cipherText));
return new String(decrypted, "UTF-8");
}
}
四、SpringBoot配置:密钥统一管理
严禁将密钥硬编码在代码中,推荐在SpringBoot配置文件中统一管理,通过@Value注入。
1. application.yml 配置
# 国密SM4配置
sm4:
# 16字节密钥(32位16进制字符串),可通过generateSm4Key()生成
key: 1234567890ABCDEF1234567890ABCDEF
# CBC模式IV向量(32位16进制字符串),可通过generateSm4Iv()生成
iv: ABCDEF1234567890ABCDEF1234567890
2. 配置类(可选,优雅注入)
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* SM4配置注入类
*/
@Component
public class Sm4Config {
@Value("${sm4.key}")
private String sm4Key;
@Value("${sm4.iv}")
private String sm4Iv;
public String getSm4Key() {
return sm4Key;
}
public String getSm4Iv() {
return sm4Iv;
}
}
五、实战场景1:接口数据加解密
最常用场景:接口请求参数加密传输、响应数据加密返回,防止数据明文泄露。
1. 编写Controller测试接口
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
@RequestMapping("/sm4")
public class Sm4Controller {
@Resource
private Sm4Config sm4Config;
/**
* 加密接口
*/
@GetMapping("/encrypt")
public String encrypt(@RequestParam String plainText) {
try {
return Sm4Util.encryptCbc(plainText, sm4Config.getSm4Key(), sm4Config.getSm4Iv());
} catch (Exception e) {
return "加密失败:" + e.getMessage();
}
}
/**
* 解密接口
*/
@GetMapping("/decrypt")
public String decrypt(@RequestParam String cipherText) {
try {
return Sm4Util.decryptCbc(cipherText, sm4Config.getSm4Key(), sm4Config.getSm4Iv());
} catch (Exception e) {
return "解密失败:" + e.getMessage();
}
}
}
2. 测试效果
- 访问加密接口:
http://localhost:8080/sm4/encrypt?plainText=国密SM4测试
返回密文:8f3b...(省略) - 访问解密接口:
http://localhost:8080/sm4/decrypt?cipherText=8f3b...
返回明文:国密SM4测试
六、实战场景2:配置文件敏感信息加密
生产环境中,数据库密码、第三方密钥等敏感信息不能明文存储,我们可以用SM4加密配置,启动时自动解密。
实现步骤:
- 先用工具类加密你的敏感信息;
- 配置文件中写入加密后的值;
- 自定义配置解密器,Spring启动时自动解密。
示例(以数据库密码为例):
spring:
datasource:
username: root
# SM4加密后的密码
password: 8f3b7d21a9c0e5f7...
进阶:可结合Spring Boot的
EnvironmentPostProcessor实现自动解密,无需手动调用工具类。
七、实战场景3:全局请求/响应自动加解密
企业级最佳实践:通过拦截器/切面实现全局自动加解密,业务代码无感知。
核心思路:
- 定义注解
@Sm4Encrypt、@Sm4Decrypt; - 用AOP切面拦截接口,自动加密响应、解密请求;
- 业务代码只处理明文,无需关注加解密逻辑。
八、关键注意事项(避坑指南)
- 密钥与IV长度必须严格16字节
SM4算法要求密钥和IV必须是16字节(128位),工具类已做校验,生成时直接使用generateSm4Key()即可。 - 模式选择
- 生产环境必须用CBC模式(带IV向量);
- ECB模式仅用于测试,禁止用于生产。
- 编码统一
加解密全程使用UTF-8、16进制编码,避免乱码。 - 密钥安全
密钥不要上传Git,生产环境使用配置中心(Nacos/Apollo)加密存储。 - 异常处理
密文被篡改、密钥错误、编码不匹配都会抛出异常,建议统一全局异常处理。
九、工具类测试用例
import org.junit.jupiter.api.Test;
import javax.annotation.Resource;
public class Sm4Test {
@Resource
private Sm4Config sm4Config;
@Test
public void testSm4() throws Exception {
// 明文
String plainText = "SpringBoot集成国密SM4";
// 加密
String cipherText = Sm4Util.encryptCbc(plainText, sm4Config.getSm4Key(), sm4Config.getSm4Iv());
System.out.println("密文:" + cipherText);
// 解密
String decryptText = Sm4Util.decryptCbc(cipherText, sm4Config.getSm4Key(), sm4Config.getSm4Iv());
System.out.println("解密后明文:" + decryptText);
}
}
十、总结
本文完整实现了SpringBoot集成国密SM4算法的全流程:
- 引入BouncyCastle依赖,支持国密算法;
- 封装通用SM4工具类,支持CBC/ECB模式;
- 完成SpringBoot配置注入、接口加解密、配置加密等实战场景;
- 给出生产环境安全规范和避坑指南。
SM4国密算法是国产化替代的核心组件,在SpringBoot项目中集成后,可满足政务、金融、医疗等行业的数据安全合规要求,是Java后端开发者必备技能。
核心知识点回顾
- 核心依赖:
bcprov-jdk15on(BouncyCastle国密支持) - 推荐模式:SM4/CBC/PKCS7Padding(带IV向量,高安全)
- 固定长度:密钥、IV均为16字节
- 最佳实践:配置中心存密钥、全局AOP自动加解密、禁止明文存储敏感信息