方案简介
前置依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>5.8.24</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.77</version>
</dependency>
注意事项:
打胖jar时,需要使用openjdk运行,使用oracleJDK运行时bcprov-jdk18on由于被打进了fatjar而导致签名不一致,从而无法正常加载运行。
如果执意要使用oracleJDK,则可以使用非fatjar运行,如果依然需要fatjar运行,则需要排除bcprov-jdk18on,调整如下:
- 调整pom依赖
bcprov-jdk18on配置
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.77</version>
<scope>provided</scope>
</dependency>
- 将
bcprov-jdk18on放入JDK的ext目录或其他JDK会直接加载的目录,或者使用springboot官方排除方法(建议采用这种方案,高版本java使用更方便):
<build>
<!--假定打包成demo_app.jar -->
<finalName>demo_app</finalName>
<plugins>
<plugin>
<!-- 启动添加loader.path,并假定bcprov在/app/目录下, -->
<!-- 示例启动参数:`java -Dloader.path=file:/app/ -jar demo_app.jar` -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude><!--直接排除bcprov-->
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</exclude>
</excludes>
<!-- 排除scope=system的包 -->
<!-- <includeSystemScope>false</includeSystemScope> -->
<!-- 必须指定layout为ZIP,否则JVM参数`-Dloader.path`无效 -->
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
- 或者使用maven插件
maven-shade-plugin打包并排除jar包里的签名
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
</excludes>
</artifactSet>
<filters>
<filter>
<artifact>*:*</artifact>
<!-- 排除所有signatures -->
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>主类全名</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
加密方案
利用cn.hutool.crypto.SmUtil工具即可加密。
sm4加密示例:
var key = System.getenv("SM4_KEY");//从环境变量传入密钥
if(key==null||key.isEmpty()) key="a1".repeat(8).getBytes();//默认密钥
System.out.println(key.length);
var sm4 = cn.hutool.crypto.SmUtil.sm4(key);
var prop = "123456";
String sm4Prop = "MD4::"+sm4.encryptHex(prop);//sm加密标识前缀+密文
System.out.println(sm4Prop);//打印加密后的字符串
var hexStr = cn.hutool.core.util.HexUtil.decodeHex(sm4Prop.substring(5));
System.out.println(sm4.decryptStr(hexStr));//打印还原的字符串
配置文件SM4解密
通过实现org.springframework.beans.factory.config.BeanFactoryPostProcessor并重写postProcessBeanFactoryy方法,在方法中使用继承自MapPropertySource并重写的getPropert方法进行SM4字符串解密
//package local.demo;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.env.*;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class PropertySourcePostProcessorMySelf implements BeanFactoryPostProcessor {
private SoftReference<SM4> sm4=null;
private SM4 sm4(){
SM4 s;
if (sm4!=null&&(s=sm4.get())!=null) return s;
// 建议通过环境变量SM4_KEY获取SM4加密KEY:
var key = System.getenv("SM4_KEY");//从环境变量获取key
if(key==null||key.isEmpty()) key="a1".repeat(8);//默认key
s = SmUtil.sm4(key.getBytes());
sm4 = new SoftReference<>(s);
return s;
}
public class SM4PropertySource extends MapPropertySource{
private MapPropertySource origin;//原配置文件对象,重写时获取值均从原配置文件对象获取
public SM4PropertySource(String name, Map<String, Object> source) {
super(name, source);
}
public SM4PropertySource(String name, Map<String, Object> source,MapPropertySource origin) {
this(name, source);
this.origin=origin;
}
@Override
public Object getProperty(String name) {
var v = origin.getProperty(name);
if(v instanceof String s && s.startsWith("MD4::")){
return sm4().decryptStr(HexUtil.decodeHex(s.substring(5)));
}
return v;
}
@Override
public boolean containsProperty(String name) {
return origin.containsProperty(name);
}
@Override
public String[] getPropertyNames() {
return origin.getPropertyNames();
}
}
@Override //SM4解密
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
MutablePropertySources propertySources = beanFactory.getBean(ConfigurableEnvironment.class).getPropertySources();
// 对application配置进行包装以完成SM4解密
propertySources.stream()
.filter(s -> s instanceof MapPropertySource && !(s instanceof SM4PropertySource) && s.getName().contains("application")) //本例中仅对application配置进行解密
// 包装所有的PropertySource
.map(s -> new SM4PropertySource(s.getName(),((MapPropertySource) s).getSource(),(MapPropertySource) s))
.toList()
// 替换掉propertySources中的PropertySource
.forEach(wrap -> propertySources.replace(wrap.getName(), wrap));
}
}