SM4(国密算法)是中国的商用密码标准,用于加密配置文件中的敏感信息。本文档详细介绍SVT项目中SM4配置加密的实现、使用和最佳实践。
- 主算法: SM4-CBC (密码分组链接模式)
- 密钥长度: 128位 (16字节)
- 分组长度: 128位 (16字节)
- 填充方式: PKCS7Padding
- 初始向量: 随机生成,16字节
// SM4Utils.java 中的配置
private static final String ALGORITHM = "SM4";
private static final String TRANSFORMATION = "SM4/CBC/PKCS7Padding";
private static final int KEY_SIZE = 16; // 128位
private static final int IV_SIZE = 16; // 128位<!-- SVT项目中的SM4依赖配置 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78.1</version>
</dependency>/**
* SM4加密工具类
* 支持CBC模式,提供配置文件加密/解密功能
*/
@Component
public class SM4Utils {
private static final Logger logger = LoggerFactory.getLogger(SM4Utils.class);
// 添加BouncyCastle安全提供者
static {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
/**
* SM4-CBC模式加密
*/
public static String encryptCBC(String plainText, String key) {
try {
// 生成密钥
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM);
// 初始化cipher
Cipher cipher = Cipher.getInstance(TRANSFORMATION, "BC");
// 生成随机IV
byte[] iv = new byte[IV_SIZE];
SecureRandom.getInstanceStrong().nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
// 加密数据
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
// 将IV和加密数据拼接后进行Base64编码
byte[] result = new byte[IV_SIZE + encrypted.length];
System.arraycopy(iv, 0, result, 0, IV_SIZE);
System.arraycopy(encrypted, 0, result, IV_SIZE, encrypted.length);
return Base64.getEncoder().encodeToString(result);
} catch (Exception e) {
logger.error("SM4-CBC加密失败", e);
throw new RuntimeException("SM4-CBC加密失败", e);
}
}
/**
* SM4-CBC模式解密
*/
public static String decryptCBC(String encryptedText, String key) {
try {
// Base64解码
byte[] data = Base64.getDecoder().decode(encryptedText);
// 提取IV和加密数据
byte[] iv = new byte[IV_SIZE];
byte[] encrypted = new byte[data.length - IV_SIZE];
System.arraycopy(data, 0, iv, 0, IV_SIZE);
System.arraycopy(data, IV_SIZE, encrypted, 0, encrypted.length);
// 生成密钥
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM);
// 初始化cipher
Cipher cipher = Cipher.getInstance(TRANSFORMATION, "BC");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
// 解密数据
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
} catch (Exception e) {
logger.error("SM4-CBC解密失败", e);
throw new RuntimeException("SM4-CBC解密失败", e);
}
}
}/**
* SM4配置解密处理器
* 实现EnvironmentPostProcessor接口,在Spring Boot启动时自动解密配置
*/
public class SM4ConfigDecryptProcessor implements EnvironmentPostProcessor {
private static final Logger logger = LoggerFactory.getLogger(SM4ConfigDecryptProcessor.class);
private static final String SM4_PREFIX = "SM4(";
private static final String SM4_SUFFIX = ")";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 获取SM4密钥
String sm4Key = environment.getProperty("SM4_KEY");
if (sm4Key == null) {
sm4Key = System.getenv("SM4_KEY");
}
if (sm4Key == null || sm4Key.trim().isEmpty()) {
logger.warn("SM4_KEY环境变量未设置,跳过SM4配置解密");
return;
}
// 处理所有配置源
for (PropertySource<?> propertySource : environment.getPropertySources()) {
if (propertySource instanceof MapPropertySource) {
processMapPropertySource((MapPropertySource) propertySource, sm4Key);
}
}
}
private void processMapPropertySource(MapPropertySource propertySource, String sm4Key) {
Map<String, Object> source = propertySource.getSource();
for (Map.Entry<String, Object> entry : source.entrySet()) {
Object value = entry.getValue();
if (value instanceof String) {
String stringValue = (String) value;
if (stringValue.startsWith(SM4_PREFIX) && stringValue.endsWith(SM4_SUFFIX)) {
try {
String encryptedValue = stringValue.substring(SM4_PREFIX.length(), stringValue.length() - SM4_SUFFIX.length());
String decryptedValue = SM4Utils.decryptCBC(encryptedValue, sm4Key);
source.put(entry.getKey(), decryptedValue);
logger.debug("成功解密配置项: {}", entry.getKey());
} catch (Exception e) {
logger.error("解密配置项失败: {}", entry.getKey(), e);
}
}
}
}
}
}SM4配置加密支持多种Spring Boot配置文件格式:
application.yml/application.yamlapplication.propertiesapplication-{profile}.yml
YAML格式(SVT项目实际配置示例):
# application-dev.yml 开发环境配置
spring:
datasource:
url: jdbc:sqlserver://localhost:1433;databaseName=svt_db;encrypt=true;trustServerCertificate=true
username: sa
password: SM4(UQcB8L5X+OABnN5xgK/PTgMrElv9pHiNkLDXZhKVGGBgKGF5MFm1wg==) # 数据库密码
data:
redis:
host: localhost
port: 6379
password: SM4(9vGNGzOVWNRqvMiL/8TJ6wxOJy7vGwmLEZWnBKjTbLo=) # Redis密码
database: 1
# JWT配置
jwt:
secret: SM4(kLJhFGvbvBNMDmWhIBzF8kCgF9gZNFTAoQ2sNP6VyXGZ7rGDlvQGqLwHXGKWBUa5B7dqJpMHWQ==)
# AES配置
svt:
security:
aes:
key: SM4(lKJhFGvbvBNMDmWhIBzF8kCgF9gZNFTAoQ2sNP6VyXGZ7rGDlvQGqLwHXGKWBUa5B7dqJpMHWQ==)Properties格式:
spring.datasource.password=SM4(UQcB8L5X+OABnN5xgK/PTgMrElv9pHiNkLDXZhKVGGBgKGF5MFm1wg==)
spring.data.redis.password=SM4(9vGNGzOVWNRqvMiL/8TJ6wxOJy7vGwmLEZWnBKjTbLo=)
jwt.secret=SM4(kLJhFGvbvBNMDmWhIBzF8kCgF9gZNFTAoQ2sNP6VyXGZ7rGDlvQGqLwHXGKWBUa5B7dqJpMHWQ==)- 格式:
SM4(加密后的文本) - 大小写敏感:
SM4必须大写 - 括号必须: 加密文本必须用圆括号包围
- 无空格:
SM4(之间不能有空格 - Base64编码: 加密后的文本使用Base64编码
Windows (PowerShell):
$env:SM4_KEY="SVT2025SM4KEY128"Windows (CMD):
set SM4_KEY=SVT2025SM4KEY128Linux/macOS:
export SM4_KEY="SVT2025SM4KEY128"Docker:
ENV SM4_KEY=SVT2025SM4KEY128Docker Compose:
services:
svt-server:
environment:
- SM4_KEY=SVT2025SM4KEY128不同环境使用不同的SM4密钥:
# 开发环境(与工具类保持一致)
export SM4_KEY="SVT2025SM4KEY128"
# UAT测试环境
export SM4_KEY="SVT2025SM4UATKEY"
# 生产环境(请使用更强的密钥)
export SM4_KEY="SVT2025SM4PRODKEY"密钥要求:
- 必须是16字节(16个字符)
- 建议使用字母、数字组合
- 不同环境使用不同密钥
SVT项目提供了专门的测试类 SM4ConfigEncryptionTool:
/**
* SM4配置加密工具
* 用于生成配置文件中敏感信息的SM4加密密文(CBC模式)
*/
public class SM4ConfigEncryptionTool {
// SM4加密密钥 (16字节/128位)
private static final String SM4_KEY = "SVT2025SM4KEY128";
/**
* 生成所有需要的配置加密值
*/
@Test
public void generateAllEncryptedConfigs() {
Map<String, String> configs = new LinkedHashMap<>();
configs.put("数据库密码", "Tsq19971108!");
configs.put("Redis密码", "123456");
configs.put("JWT密钥", "SVT-DEV-JWT-SECRET-CHANGE-ME-5678-VERY-LONG-AND-SECURE");
configs.put("AES密钥", "wJ/6sgrWER8T14S3z1esg39g7sL8f8b+J5fCg6a5fGg=");
for (Map.Entry<String, String> entry : configs.entrySet()) {
encryptAndPrint(entry.getKey(), entry.getValue());
}
}
/**
* 生成YAML配置示例
*/
@Test
public void generateYamlExample() {
String dbPassword = SM4Utils.encryptCBC("Tsq19971108!", SM4_KEY);
String redisPassword = SM4Utils.encryptCBC("123456", SM4_KEY);
String jwtSecret = SM4Utils.encryptCBC("SVT-DEV-JWT-SECRET-CHANGE-ME-5678-VERY-LONG-AND-SECURE", SM4_KEY);
String aesKey = SM4Utils.encryptCBC("wJ/6sgrWER8T14S3z1esg39g7sL8f8b+J5fCg6a5fGg=", SM4_KEY);
System.out.println("# application-dev.yml");
System.out.println("spring:");
System.out.println(" datasource:");
System.out.println(" password: SM4(" + dbPassword + ")");
System.out.println(" data:");
System.out.println(" redis:");
System.out.println(" password: SM4(" + redisPassword + ")");
System.out.println("jwt:");
System.out.println(" secret: SM4(" + jwtSecret + ")");
System.out.println("svt:");
System.out.println(" security:");
System.out.println(" aes:");
System.out.println(" key: SM4(" + aesKey + ")");
}
}方法一:运行测试类(推荐)
# 1. 设置环境变量
export SM4_KEY="SVT2025SM4KEY128"
# 2. 运行测试生成加密值
mvn test -Dtest=SM4ConfigEncryptionTool#generateAllEncryptedConfigs
# 3. 查看控制台输出的加密值方法二:使用独立工具文件
# 1. 编译并运行独立的加密工具
cd SVT-Server
javac -cp target/classes GenerateSM4Configs.java
java -cp target/classes:. GenerateSM4Configs
# 2. 查看输出结果方法三:集成到应用中
// 在Spring Boot应用中直接调用
public class ConfigEncryptionService {
@Value("${SM4_KEY}")
private String sm4Key;
public String encryptConfig(String plainText) {
return SM4Utils.encryptCBC(plainText, sm4Key);
}
public String decryptConfig(String encryptedText) {
return SM4Utils.decryptCBC(encryptedText, sm4Key);
}
}Maven启动:
export SM4_KEY="SVT2025SM4KEY128"
mvn spring-boot:runJAR包启动:
export SM4_KEY="SVT2025SM4KEY128"
java -jar SVT-Server-1.0.1-SNAPSHOT.jar系统服务配置 (systemd):
[Unit]
Description=SVT Server Application
After=network.target
[Service]
Type=forking
User=svt
Environment=SM4_KEY=SVT2025SM4KEY128
ExecStart=/usr/bin/java -jar /opt/svt/SVT-Server-1.0.1-SNAPSHOT.jar
Restart=always
[Install]
WantedBy=multi-user.targetDockerfile:
FROM openjdk:21-jre-slim
COPY target/SVT-Server-1.0.1-SNAPSHOT.jar app.jar
ENV SM4_KEY=""
ENTRYPOINT ["java", "-jar", "/app.jar"]Kubernetes Secret:
apiVersion: v1
kind: Secret
metadata:
name: svt-sm4-secret
type: Opaque
data:
sm4-key: <base64-encoded-key>
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: svt-server
spec:
template:
spec:
containers:
- name: svt-server
image: svt-server:latest
env:
- name: SM4_KEY
valueFrom:
secretKeyRef:
name: svt-sm4-secret
key: sm4-key错误1: 未设置环境变量
Error: SM4_KEY环境变量未设置,跳过SM4配置解密
解决: 设置环境变量 SM4_KEY
错误2: 解密失败
Error: SM4-CBC解密失败
解决:
- 检查环境变量是否正确设置
- 确认加密值格式正确
SM4(...) - 验证密钥长度为16字节
- 检查Base64编码是否正确
错误3: 配置文件格式错误
Error: Could not resolve placeholder 'SM4(xxx)'
解决:
- 检查
SM4()格式是否正确 - 确认没有多余的空格
- 验证YAML缩进是否正确
错误4: BouncyCastle提供者未找到
Error: java.security.NoSuchAlgorithmException: SM4 KeyGenerator not available
解决:
- 确认BouncyCastle依赖已正确添加
- 检查安全提供者是否正确注册
启用SM4调试日志:
logging:
level:
com.seventeen.svt.common.config.SM4ConfigDecryptProcessor: DEBUG
com.seventeen.svt.common.util.SM4Utils: DEBUG启动时的日志验证:
2025-01-XX XX:XX:XX.XXX INFO --- [main] c.s.s.c.c.SM4ConfigDecryptProcessor : 成功解密配置项: spring.datasource.password
2025-01-XX XX:XX:XX.XXX INFO --- [main] c.s.s.c.c.SM4ConfigDecryptProcessor : 成功解密配置项: spring.data.redis.password
2025-01-XX XX:XX:XX.XXX INFO --- [main] c.s.s.c.c.SM4ConfigDecryptProcessor : 成功解密配置项: jwt.secret
✅ 推荐做法:
- 使用16字节强密钥(必须严格16字符)
- 不同环境使用不同密钥
- 定期轮换密钥
- 使用密钥管理服务存储生产密钥
- 生产环境密钥示例:
SVT2025SM4PRODKEY
❌ 避免做法:
- 将密钥硬编码在代码中
- 使用弱密钥或测试密钥
- 密钥长度不是16字节
- 在日志中输出密钥
✅ 推荐做法:
- 只加密敏感配置(密码、密钥、Token)
- 使用CBC模式提高安全性
- 每次加密使用随机IV
- 定期审查加密的配置项
❌ 避免做法:
- 加密所有配置项
- 重复使用相同的IV
- 忽略加密算法的安全更新
✅ 推荐做法:
- 限制对环境变量的访问权限
- 使用容器secrets管理密钥
- 监控配置文件的访问和修改
- 定期安全审计
❌ 避免做法:
- 在进程列表中暴露密钥
- 通过不安全的渠道传输密钥
- 在版本控制中提交密钥
第一步:设置环境变量
# Windows (PowerShell)
$env:SM4_KEY="SVT2025SM4KEY128"
# Linux/macOS
export SM4_KEY="SVT2025SM4KEY128"第二步:运行测试生成加密值
cd SVT-Server
mvn test -Dtest=SM4ConfigEncryptionTool#generateAllEncryptedConfigs第三步:复制加密值到配置文件
# 将测试输出的 SM4(...) 值复制到 application-{profile}.yml
spring:
datasource:
password: SM4(generated_encrypted_value_here)第四步:启动应用验证
# 确保环境变量已设置,然后启动应用
mvn spring-boot:run已加密的配置项:
- 数据库连接密码 (
spring.datasource.password) - Redis连接密码 (
spring.data.redis.password) - JWT签名密钥 (
jwt.secret) - AES加密密钥 (
svt.security.aes.key)
配置文件位置:
- 开发环境:
application-dev.yml - UAT环境:
application-uat.yml - 生产环境:
application-prod.yml
与AES API加密协同:
// SM4: 配置文件敏感信息加密
// AES: API请求响应数据加密
// 两者使用不同的密钥和用途,相互独立工作与Argon2密码哈希协同:
// SM4: 配置文件加密
// Argon2: 用户密码哈希
// 两者提供不同层面的安全保护| 特性 | SM4 | Jasypt |
|---|---|---|
| 算法标准 | 国密SM4 | 国际标准AES |
| 密钥长度 | 128位固定 | 256位可配 |
| 加密模式 | CBC | 多种模式 |
| 性能 | 高 | 中等 |
| 合规性 | 符合国密标准 | 符合国际标准 |
从Jasypt迁移到SM4:
- 使用SM4工具类重新生成所有加密值
- 更新配置文件格式
ENC(...)→SM4(...) - 更新环境变量
JASYPT_ENCRYPTOR_PASSWORD→SM4_KEY - 重新部署应用
保持双重支持:
- 可以同时支持SM4和Jasypt加密
- 逐步迁移配置项
- 保持向后兼容性
SM4配置加密为SVT项目提供了符合国密标准的配置文件加密能力。关键要点:
- 国密合规: 使用SM4国密算法,符合国内安全标准
- 简单易用: 16字节密钥,简化配置管理
- 高性能: CBC模式提供良好的安全性和性能平衡
- 自动化: 启动时自动解密,无需手动干预
- 开发友好: 专用工具类便于开发和测试
SVT项目SM4使用流程:
- 开发阶段:使用工具类生成加密值
- 配置阶段:将加密值配置到环境文件
- 部署阶段:设置SM4_KEY环境变量
- 运维阶段:定期轮换密钥,监控安全状态
遵循本文档的指导,可以确保SVT项目配置文件加密的安全性、合规性和可维护性。