SpringBoot配置文件SM4加密解密

417 阅读2分钟

方案简介

前置依赖

<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,调整如下:

  1. 调整pom依赖bcprov-jdk18on配置
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk18on</artifactId>
    <version>1.77</version>
    <scope>provided</scope>
</dependency>
  1. 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>
  1. 或者使用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));
    }
}