1. 先看现象:本地一切正常,Pod里解密报错
1.1 一个非常典型的事故现场
先描述一个我见过很多次的场景:本地IDE里用Hutool的SmUtil.sm4()加解密、接口联调全都没问题,单元测试也绿油油一片。代码推到CI,镜像一构建,部署到K8s之后,服务方立刻反馈“接口返回的解密结果不对”“解密直接抛异常”。你连上Pod一看日志:
java.security.NoSuchAlgorithmException: Cannot find any provider supporting SM4/ECB/PKCS5Padding或者:
java.security.NoSuchProviderException: no such provider: BC有些更隐蔽,直接抛IllegalAccessError,堆栈指向org.bouncycastle.*。你反复检查代码,加解密逻辑明明和本地一模一样,Hutool版本也一致,那问题究竟出在哪?
先说结论:Hutool本身没有bug,K8s也不是“背锅侠”,绝大多数这类故障都出在Bouncy Castle Provider(简称BC)的依赖、注册和JDK版本兼容性上。本地能跑是因为开发环境“太脏”——IDE全局库里有BC的旧jar,或者本地Maven仓库缓存把缺的依赖悄悄补上了。而K8s镜像是一个相对干净的执行环境,缺什么、不兼容什么,都会直接暴露出来。
这篇文章我会把SM4在K8s环境解密失败这件事掰开揉碎,从JCA的Provider机制讲起,再到依赖声明、代码注册、安全配置文件修改、镜像构建、启动自检,最后给一份异常速查表。即使你完全没碰过Bouncy Castle,也能照着把问题解决。
1.2 为什么K8s会放大这个问题:五个最常见根因
在我处理过的类似问题里,根因基本逃不出下面五类。按出现频率排序:
第一类:BC依赖根本没进Final Jar。Hutool的crypto模块把Bouncy Castle的依赖标成了optional,意思是“我不替你传递引入,用的时候你自己加”。本地IDE里跑得通,多半是之前某次调试时本地仓库里存了BC的jar,IDE自动帮你挂到了classpath;或者项目里某个测试依赖、插件顺手把BC拖了进来。一旦走Maven/Gradle的release构建,optional依赖不会被传递,Fat Jar里压根没有bcprov-*.jar,运行时就抛NoClassDefFoundError: org/bouncycastle/jce/provider/BouncyCastleProvider。
第二类:BC类在,但Provider没注册到JVM。JCA(Java Cryptography Architecture)的机制是:算法标准接口摆在明面上,真正干活的是“Provider”。你要用SM4,但JVM的Security.getProviders()列表里没有BC这个Provider,那Cipher.getInstance()就找不到实现。Hutool新版本虽然会在静态块里尝试自动注册BC,但如果代码是在某些特殊封装、自定义类加载器或模块化环境下运行,这套隐式逻辑可能没生效,或者注册顺序不对。
第三类:BC版本和Pod里的JDK版本不匹配。这是JDK 17场景最坑的一条。老版本bcprov-jdk15on(比如1.60到1.69)在JDK 17的强封装机制下,可能触发IllegalAccessError、InaccessibleObjectException,表面症状就是SM4解密失败。本地用的是JDK 8当然没事,镜像里却是JDK 17,同一条代码跑出两种结果。
第四类:自定义的java.security配置覆盖了Provider。有些人喜欢在Dockerfile里往$JAVA_HOME/conf/security/java.security里追加一行注册BC,或者用-Djava.security.properties=...注入自定义安全策略,再或者用ConfigMap直接挂载到JDK的security目录。这些操作如果编号冲突、语法写错、或者整个目录被volume覆盖,BC就悄悄从Provider列表里消失了。
第五类:熵源导致的初始化阻塞。极端情况下,容器里/dev/random熵不足,BC初始化或SecureRandom获取随机数时线程长时间阻塞,Pod启动探针超时,看起来像“卡死”或者“解密失败”。这种情况多见于JDK 8镜像,JDK 17默认已经改走/dev/urandom,反而少见。
这五类原因经常叠加出现。比如:pom里漏了BC依赖,你又顺手写了个-Djava.security.properties去加固环境,两个问题一起爆,排查难度直接翻倍。下一章先把JCA这套机制说透,你才知道配置到底在配什么。
2. 搞懂底层机制:JCA、Provider与Hutool的SM4
2.1 JCA/JCE的Provider机制
把JCA想象成餐厅的菜单,菜单上写着“SM4/ECB/PKCS5Padding”这道菜,Cipher是服务员,Provider是后厨。你对着菜单点菜(调用Cipher.getInstance("SM4/ECB/PKCS5Padding")),服务员要去后厨问一圈谁会做。如果整个餐厅只有一个后厨团队,他们不会做这道菜,服务员就回来告诉你:NoSuchAlgorithmException。如果有个后厨团队叫“BC”明明在,但前厅点单系统里根本没登记这个团队,服务员连问都不会去问,直接告诉你:no such provider: BC。
Java里的Provider本质上就是一组“算法实现”的注册表。JVM启动时,会读取java.security配置文件里security.provider.N=那一串类名,按顺序加载这些Provider。运行时你也能通过代码动态注册:
Security.addProvider(new BouncyCastleProvider());注册之后,Security.getProviders()里就能看到BC,Cipher在查找算法时才有机会找到SM4的对应实现。
这里有个细节容易误导人:Cipher.getInstance("SM4/ECB/PKCS5Padding")不带Provider参数时,JVM是按Provider注册顺序遍历的,谁先支持就用谁。如果系统里同时存在两个支持SM4的Provider(比如BC和一个自研安全SDK),顺序就决定了你实际用的是哪家实现。用Cipher.getInstance("SM4/ECB/PKCS5Padding", "BC")显式指定Provider可以避开这种不确定性。Hutool内部在调用JCE时基本都会锁定BC,但你自己手写JCE代码时可要注意这一点。
2.2 Hutool的SM4到底依赖什么
Hutool的SmUtil、SM4类本质上是JCE之上的一层友好封装。你写:
SM4 sm4 = SmUtil.sm4(keyBytes); byte[] encrypted = sm4.encrypt(plainBytes); byte[] decrypted = sm4.decrypt(encrypted);底层流程大致是:先确保BouncyCastleProvider已注册,然后通过Cipher.getInstance("SM4/ECB/PKCS5Padding", "BC")拿到Cipher实例,再做加解密操作。
关键问题就出在“确保已注册”这步。Hutool的较新版本确实会在SmUtil的静态块里尝试把BC加到Security,类似:
if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); }但你要注意,这段代码的执行前提是:BC的jar必须存在于classpath。jar都不在,类加载阶段直接就NoClassDefFoundError了,后面注册逻辑根本没机会跑。另外在某些被二次封装、插件化改造、或者使用独立ClassLoader的环境里,这个静态块是否按时执行、执行后是否对当前ClassLoader生效,都要打问号。
所以我不建议你赌Hutool的自动注册。最稳妥的做法是:显式声明BC依赖,同时在应用启动最早期主动注册一次Provider。这样不管Hutool内部怎么变,你的SM4调用永远有兜底。
2.3 JDK 8与JDK 17的差异,为什么版本漂移更容易出事
本地开发和K8s镜像用不同JDK版本,是这类问题最大的隐形推手。JDK 8和JDK 17在加密这块有几个明显差异:
安全配置文件路径不同。JDK 8的默认安全配置在$JAVA_HOME/lib/security/java.security,JDK 9之后变成了$JAVA_HOME/conf/security/java.security。很多人照着网上的JDK 8教程改配置,在JDK 17容器里改错了位置,注册了个寂寞。
模块化与强封装。JDK 9引入模块系统,JDK 17默认开启强封装。老版本BC库为了性能用了一些反射、Unsafe技巧,在JDK 17下可能直接被拒,抛出IllegalAccessError。症状看起来像解密失败,但根因是BC库本身太老。
Provider默认列表变化。不同JDK版本默认加载的Provider数量不一样,security.provider.N的编号也不同。你在网上抄一段“把BC配成security.provider.11”的配置,在某种JDK版本下可能正好冲突,在另一种版本下又没问题。
理解了这些差异,你就能明白为什么我强烈建议:本地开发用的JDK大版本要和K8s镜像保持一致。不是不能跨版本,而是跨版本之后,所有Provider、模块、加密策略的差异都会变成潜在的坑。
3. BC提供者完整配置指南(四套方案+一条铁律)
3.1 方案一:代码层注册,最保底的解法
代码注册是兼容性最好、最不容易受环境影响的方案。核心就一段逻辑:
import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; public final class CryptoBootstrap { private CryptoBootstrap() { } public static void ensureBcProvider() { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { Security.addProvider(new BouncyCastleProvider()); } } }调用时机很关键。对Spring Boot应用,我通常建议放在启动类的静态块里,这是JVM类加载阶段最早能执行的业务代码,早于任何Bean初始化、任何加密操作:
@SpringBootApplication public class Application { static { CryptoBootstrap.ensureBcProvider(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }为什么要先Security.getProvider("BC")判断再添加?因为重复添加会产生两个BouncyCastleProvider实例,虽然不报错,但会在Provider列表里产生冗余,某些场景下干扰Provider遍历顺序。BC的PROVIDER_NAME常量就是字符串"BC",直接用常量更安全。
这方案的好处是:不依赖镜像里改文件、不依赖JVM参数、不容易被外部配置覆盖。坏处是:每个需要执行SM4的应用都得带上这段代码,属于“人肉基建”。等会儿我在最后一章会讲怎么把它沉淀成团队公共组件。
3.2 方案二:修改JVM安全配置文件java.security
如果你希望整个JVM范围内所有应用都能用BC,可以改JDK的安全配置文件。JDK 8路径是$JAVA_HOME/lib/security/java.security,JDK 9及以上版本是$JAVA_HOME/conf/security/java.security。
找到文件里security.provider.N=开头的区域,找一个还没被占用的编号,追加一行:
security.provider.12=org.bouncycastle.jce.provider.BouncyCastleProvider注意编号必须唯一,不能和已有行重复。不同JDK版本默认的Provider数量不一样,JDK 8可能是10个,JDK 17可能更多,先看一眼文件确认再写。
这种方式最稳定,但有两个问题:第一,改的是镜像内JDK目录,如果运维用ConfigMap或InitContainer挂载覆盖了$JAVA_HOME/conf/security,你的修改会被整体顶掉;第二,不同基础镜像的JDK目录布局可能不同,Dockerfile里不能写死路径。所以写成Dockerfile时,最好先动态确认JAVA_HOME:
RUN echo "security.provider.12=org.bouncycastle.jce.provider.BouncyCastleProvider" >> $JAVA_HOME/conf/security/java.security$JAVA_HOME在镜像里如果已经通过ENV设置,直接用就行;如果不确定,可以在构建时先RUN java -XshowSettings:properties -version 2>&1 | grep java.home看路径。
3.3 方案三:JVM启动参数注入自定义安全属性
有些场景不允许改镜像内容,那就用JVM参数。Java提供了java.security.properties启动参数:
java -Djava.security.properties=/app/conf/extra.security -jar app.jar单等号的含义是“在默认安全策略之上叠加指定的文件”,双等号是“完全替换默认安全策略”:
java -Djava.security.properties==/app/conf/extra.security -jar app.jar单等号看似方便,实际用起来容易踩坑:你自定义的extra.security里如果写了security.provider.12,它的生效语义和数组合并规则在不同JDK版本下并不完全一致,有时会直接把整个Provider数组覆盖成你只写的这一条。所以我的建议是:如果你只是单纯想加BC,优先用方案一或方案二,方案三留给你确有必要做安全策略裁剪的场景,而且要在测试环境充分验证。
3.4 方案四:Spring Boot场景下的自动注册
既然前面说了静态块是最早的注册时机,为什么还要单独写一个配置类?因为静态块方案要求每个开发都记得写,而配置类可以做成公用的starter或模块,团队复用成本低。
一个典型的自动配置类长这样:
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; import java.security.Security; @Configuration public class BouncyCastleAutoConfiguration { @PostConstruct public void registerProvider() { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { Security.addProvider(new BouncyCastleProvider()); } } }但我要提醒一个时序陷阱:@PostConstruct在Spring容器刷新过程中才执行。如果你的应用里有更早发生的加密动作,比如某个BeanFactoryPostProcessor、@Value解密占位符、第三方框架初始化时就调用了SM4,那这个配置类可能来不及兜底。所以配置类可以作为“通用保障”,而关键业务入口的启动类静态块仍然建议保留。
如果你用Spring Boot 3.x,注意javax.annotation.PostConstruct要换成jakarta.annotation.PostConstruct,别引错包。
3.5 K8s镜像与打包层面的专项配置
进到K8s这个执行环境,光有代码注册还不够,有些坑出在“依赖根本没进镜像”和“Jar内部的Provider描述文件被合并坏掉”。按应用类型分两种情况:
Spring Boot标准Fat Jar。所有依赖jar嵌套在BOOT-INF/lib下,每个jar都有自己的META-INF/services/java.security.Provider文件,互不覆盖。这类应用最常踩的坑是pom里漏了BC依赖。排查命令很直接:
unzip -l app.jar | grep bcprov如果没有输出,就是BC没打进去。解决方式是在pom里显式声明:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk18on</artifactId> <version>1.77</version> </dependency>这样Hutool的optional依赖就变成显式依赖了,Fat Jar必然带上BC。
maven-shade-plugin打的Uber Jar。如果你用shade把所有类合并成一个大jar,BC jar里的META-INF/services/java.security.Provider文件在合并时可能被互相覆盖,导致BC的Provider描述丢失。这类应用需要在shade插件里加ServicesResourceTransformer:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.5.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> </transformers> </configuration> </execution> </executions> </plugin>K8s层面还有一个容易忽略的检查项:容器里通过-Djava.security.properties、环境变量、ConfigMap挂载悄悄改变了安全配置。有时候BI团队为了合规会统一挂一套安全基线配置,把security.provider.N整个覆盖了,BC自然就没了。排查时多看一眼kubectl describe pod xxx里的Volumes和Environment。
3.6 BC依赖版本选择:jdk15on还是jdk18on
BC的坐标名看着就让人头大。老一代的是bcprov-jdk15on,曾经长期用于JDK 5到JDK 14;后来BC官方推出了bcprov-jdk18on,这个jdk18on并不是“只支持JDK 18”,而是面向JDK 8及以后版本的新版本线。官方在新版本线里持续添加新算法和修复,老版本线基本只做维护。
给一个最简单的选型建议:
| 运行JDK版本 | 推荐BC坐标 | 版本示例 |
|---|---|---|
| JDK 8 | bcprov-jdk18on | 1.77 |
| JDK 11 | bcprov-jdk18on | 1.77 |
| JDK 17 | bcprov-jdk18on | 1.77 |
| JDK 21 | bcprov-jdk18on | 1.77 |
如果项目历史包袱重,还在用bcprov-jdk15on,至少也要升到1.70以上的版本,并在JDK 17环境完整回归一遍。我见过太多次“本地JDK 8 + bcprov 1.68正常,镜像JDK 17直接IllegalAccessError”的案例,这个版本错配问题在K8s里特别容易爆发。
版本管理上,建议用dependencyManagement统一锁定,避免Hutool传递上来的不一致版本打断你的手脚:
<dependencyManagement> <dependencies> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk18on</artifactId> <version>1.77</version> </dependency> </dependencies> </dependencyManagement>一条铁律总结:依赖显式声明、Provider显式注册、JDK版本显式一致。做到这三点,90%的SM4 K8s失败问题根本不会发生。
4. 验证与排错:从异常反推问题,一表搞定
4.1 五种验证BC是否注册成功的方法
写完配置,怎么确认真的生效?下面几个方法按验证成本从低到高排列。
方法一:应用启动时打印Provider列表。在CryptoBootstrap.ensureBcProvider()执行后,加一行日志:
for (Provider provider : Security.getProviders()) { System.out.println("Provider: " + provider.getName() + " v" + provider.getVersion()); }日志里能看到BC这一行,说明注册成功。这方法零成本,建议直接固化在公共组件里,每个应用启动都能看到。
方法二:写一个SM4往返自检。用固定密钥做一次加密再解密,结果不一致或抛异常就说明环境有问题:
import cn.hutool.core.util.CharsetUtil; import cn.hutool.crypto.symmetric.SM4; import cn.hutool.crypto.symmetric.SmUtil; import java.security.Security; public class Sm4SelfCheck { public static void main(String[] args) { CryptoBootstrap.ensureBcProvider(); byte[] key = "0123456789abcdef".getBytes(CharsetUtil.CHARSET_UTF_8); SM4 sm4 = SmUtil.sm4(key); byte[] encrypted = sm4.encrypt("hello-sm4".getBytes(CharsetUtil.CHARSET_UTF_8)); byte[] decrypted = sm4.decrypt(encrypted); String result = new String(decrypted, CharsetUtil.CHARSET_UTF_8); if (!"hello-sm4".equals(result)) { throw new IllegalStateException("SM4 self-check failed"); } System.out.println("SM4 self-check passed"); } }Spring Boot应用可以把这段逻辑做成ApplicationRunner,启动自检失败就让容器进入CrashLoopBackOff,把问题暴露在启动阶段。
方法三:在Pod内执行验证类。手动验证时注意,Spring Boot Fat Jar里嵌套的依赖不会自动加到java -cp app.jar的classpath。正确姿势是:
kubectl exec -it <pod> -- sh -c 'java -cp "/app/BOOT-INF/classes:/app/BOOT-INF/lib/*" Sm4SelfCheck'前提是镜像里有java、有sh、路径符合Spring Boot默认结构。如果镜像太精简连shell都没有,就在应用的启动日志里看Provider打印,或者临时用调试探针打开。
方法四:查看java.security配置是否生效。进到Pod里执行:
grep -n "security.provider" $JAVA_HOME/conf/security/java.security先确认JAVA_HOME路径真实存在,可以用:
java -XshowSettings:properties -version 2>&1 | grep java.home方法五:手工运行Security.getProviders()。JDK 9+可以用jshell快速验证:
jshell import java.security.*; Security.getProviders();JDK 8没有jshell,用方法二最直接。
4.2 SM4相关异常速查表
| 异常/报错 | 最可能原因 | 快速定位 | 处理方向 |
|---|---|---|---|
NoClassDefFoundError: org/bouncycastle/jce/provider/BouncyCastleProvider | bcprov jar不在运行时classpath | unzip -l app.jar | grep bcprov | pom显式声明BC依赖 |
ExceptionInInitializerError+ 上面堆栈 | SmUtil类静态初始化时加载BC失败 | 同上 | 同上,并保证类初始化顺序 |
NoSuchProviderException: no such provider: BC | BC类存在但未注册到Security | 打印Provider列表 | 代码注册或改java.security |
NoSuchAlgorithmException: Cannot find any provider supporting SM4... | 未注册BC,或BC版本太老不支持SM4 | 确认Provider列表和BC版本 | 注册BC并升级到1.71+ |
IllegalAccessError/InaccessibleObjectException指向org.bouncycastle | 老BC版本在JDK 17强封装下崩溃 | java -version对比本地和Pod | 换bcprov-jdk18on,统一JDK版本 |
IllegalBlockSizeException: last block incomplete in decryption | 密文缺失、Base64处理不一致、加解密模式不匹配 | 对比两端数据和模式 | 核对ECB/CBC、填充、Base64 |
| 解密出乱码或长度不对 | 密钥/IV不一致,或字符串密钥被隐式转换 | 对比两端密钥派生逻辑 | 统一密钥处理方式 |
| 启动卡死,探针超时 | 容器熵源不足 | 线程dump看是否阻塞在SecureRandom | JDK 8加-Djava.security.egd=file:/dev/urandom,升级JDK 17 |
java.lang.SecurityException: JCE cannot authenticate the provider BC | BC jar被篡改或签名问题 | 校验jar完整性 | 从Maven中央仓库重新拉取 |
补充一个不算异常但很容易让人困惑的点:SM4密钥长度是128位,也就是16字节,不受JDK老版本里“无限强度策略文件”的限制。如果你在日志里看到InvalidKeyException: Illegal key size,多半是同一段代码里用了AES-256之类的算法,而不是SM4本身的问题。别被误导到去下载策略包,先分清是哪条链路上报的错。
4.3 K8s环境专项排查步骤清单
K8s里排错,我习惯按下面这个顺序走,每一步都有明确结论产出:
- 对比JDK版本。本地执行
java -version,Pod里执行kubectl exec -it <pod> -- java -version。大版本不一致,先解决版本漂移问题。 - 检查依赖树。本地跑
mvn dependency:tree -Dincludes=org.bouncycastle,确认BC的坐标和版本;再看是optional还是compile。 - 检查Fat Jar内容。把Pod里的jar拷出来,或者直接在Pod内用unzip查看,确认
bcprov-*.jar存在,且不止一个版本时要注意冲突。 - 检查启动参数和环境变量。
kubectl exec -it <pod> -- env | grep -i java,看有没有JAVA_TOOL_OPTIONS、JAVA_OPTS里带-Djava.security.*的参数。 - 检查挂载与Volume。
kubectl describe pod <pod>,重点看是否有ConfigMap或PVC挂到了$JAVA_HOME或JDK的security目录。 - 检查Provider列表。通过启动日志或自检类,确认Pod里的
Security.getProviders()是否包含BC,以及BC版本号。 - 做一次Pod内自检。用4.1里的方法二,在Pod内直接跑SM4往返加密,把故障锁定在“环境”而不是“代码”。
这套流程走下来,基本能把问题定位到具体类别。如果全走完还是不行,再考虑极端情况:应用里有自定义ClassLoader隔离了BC、或者某个AOP切面对Security做了拦截。不过说实话,这类极端情况占比很低,大部分问题都出在前三步。
5. 我踩过的坑和最终建议
5.1 三个让我印象深刻的坑
第一个坑是某次事故:应用在本地JDK 8上开发,测试也是JDK 8环境,发布到K8s测试环境后解密接口间歇性失败。查了半天发现Pod里JDK 17,BC版本还停留在1.68,IllegalAccessError在日志里被框架吞了一部分,只露出了“decrypt failed”的业务封装。最后全链路统一到bcprov-jdk18on:1.77,这个问题再没出现过。事后复盘,根因就是开发和运行环境割裂。
第二个坑是Hutool的optional依赖。项目pom里只声明了Hutool,SmUtil在本地一切都好,CI机器重新拉依赖后Fat Jar里没有bcprov,直接NoClassDefFoundError。因为本地Maven仓库里某个老版本的BC jar被IDE顺手加进了classpath,掩盖了依赖缺失。从那次以后,我所有用到SmUtil的pom都会显式声明BC,不依赖传递。
第三个坑是挂载覆盖。某团队在Dockerfile里正确地向$JAVA_HOME/conf/security/java.security追加了BC,但后来K8s侧为了统一安全基线,把一个ConfigMap挂到了$JAVA_HOME/conf/security目录,整个目录被替换,BC注册行被还原成默认状态。这是典型的环境噪音,排查时如果不看kubectl describe的Volume,很容易怀疑到错误方向。
5.2 团队落地的一揽子建议
这几轮踩坑之后,我在团队里推行了几项硬性约定,效果很明显。第一,公共组件化。把CryptoBootstrap注册逻辑、版本锁定、启动自检封装成一个crypto-common模块,新服务直接引入,BC注册和SM4自检自动生效。第二,版本锁死。在父pom的dependencyManagement里锁定bcprov-jdk18on版本,禁止各业务线私自升级或降级。第三,环境一致性。CI流水线里加一个检查脚本,对比开发者本地JDK版本和镜像构建用的JDK版本,不一致直接阻断。第四,启动即验证。所有使用国密算法的服务,务必在启动阶段执行一次SM4往返自检,失败就fail-fast,而不是等到上游调用方来报错你才发现。
我个人在实际操作中的体会是:这类问题之所以折磨人,不在于技术有多深,而在于“本地正常”这个假象消耗了大量排查时间。早点把Provider注册、依赖声明、JDK版本一致性做成默认动作,K8s环境下的SM4加解密才不会成为团队日常的隐形负担。