SpringBoot项目Jar包加密,防止反编译
最近项目要求部署到其他公司的服务器上,但是又不想将源码泄露出去。要求对正式环境的启动包进行安全性处理,防止客户直接通过反编译工具将代码反编译出来。
第一种方案使用代码混淆
采用proguard-maven-plugin插件
在单模块中此方案还算简单,但是现在项目一般都是多模块,一个模块依赖多个公共模块。那么使用此方案就比较麻烦,配置复杂,文档难懂,各模块之间的调用在是否混淆时极其容易出错。
第二种方案使用代码加密
采用classfinal-maven-plugin插件
此方案比对上面的方案来说,就简单了许多。直接配置一个插件就可以实现源码的安全性保护。并且可以对yml、properties配置文件以及lib目录下的maven依赖进行加密处理。若想指定机器启动,支持绑定机器,项目加密后只能在特定机器运行。
ClassFinal项目源码地址
只需要在启动类的pom.xml文件中加如下插件即可,需要注意的是,改插件时要放到spring-boot-maven-plugin插件后面,否则不起作用。
无密码启动
java -javaagent:xxx-encrypted.jar -jar xxx-encrypted.jar
有密码启动
java -javaagent:xxx-encrypted.jar=\’-pwd=密码\’ -jar xxx-encrypted.jar
启动包加密之后,方法体被清空,保留方法参数、注解等信息.主要兼容swagger文档注解扫描
反编译只能看到方法名和注解,看不到方法体的具体内容
启动过程中解密class,完全内存解密,不留下任何解密后的文件
yml配置文件留下空白
下载到classfinal-fatjar-1.2.1.jar依赖,在当前依赖下cmd执行java -jar classfinal-fatjar-1.2.1.jar -C命令,会自动生成一串机器码
将此生成好的机器码,放到maven插件中的code里面即可。这样,打包好的项目只能在生成机器码的机器运行,其他机器则启动不了项目。
实现 SpringBoot 程序加密,禁止 jadx 反编译
toB 的本地化 java 应用程序,通常是部署在客户机器上,为了保护知识产权,我们需要将核心代码(例如 Lience,Billing,Pay 等)进行加密或混淆,防止使用 jadx 等工具轻易反编译。同时,为了更深层的保护程序,也要防止三方依赖细节被窥探;
1.ProGuard[1]
- 简介:开源社区有名的免费混淆工具,相较于字节码加密,对性能基本无影响;
- 优势:打包阶段混淆字节码,各种变量方法名都变成了abcdefg 等等无意义的符号,字节码可被反编译,但几乎无法阅读,通常被 Android App 用来防止逆向;
- 不足1:只能混淆部分代码,打包阶段较为耗时,对于三方包混淆,并没有什么好办法。
- 不足2:混淆后的代码,会影响 arthas 工具的使用,导致排查问题变慢。
- 不足3:配置比较复杂,曾经在我司 T 项目上用过,令人眼花缭乱。
- 不足4:无法加密三方依赖所有信息;
2.jar-protect[2]
- 简介:一款国人开发的 springboot jar 加密工具;需要配合 javaagent 解密;
- 优势:打包阶段使用 javassist 重写 class 文件;jadx 反编译后看到的都是空方法。反编译后只能看到类信息和方法签名,无法看到具体内容。
- 不足1:使用 DES 方案,对于几百个三方 jar 的场景,加密手段过重,且加密后的不够完整;
- 不足2:类文件放在一个目录(META-INF/.encode/),非常容易类冲突;
- 不足3:无法加密三方依赖所有信息;
3.GraalVM[3]
- 简介:Oracle GraalVM 提前将 Java 应用程序编译为独立的二进制文件。与在 Java 虚拟机 (JVM) 上运行的应用程序相比,这些二进制文件更小,启动速度提高了 100 倍,无需预热即可提供峰值性能,并且使用的内存和 CPU 更少, 并且无法反编译。
- 不足:无法支持我司业务程序框架。
4.core-lib/xjar[4]
- 简介:国人开源的,基于 golang 的加密工具。使用 maven 插件加密,启动时 golang 解密;性能影响未知。
- 优势:可对所有 class 文件加密。
- 不足1: 加密后 jar 文件体积翻倍;
- 不足2:依赖 golang 编译,依赖 golang 启动;
- 不足3:无法加密三方依赖所有信息;
- 不足4:开源项目,3年未有新提交。
我们的需求到底是什么?a:保护知识产权。具体手段为:
- 对本司项目代码进行加密,使其无法被 jadx 工具轻易反编译,
- 对本司三方依赖进行加密,使其无法窥探我司三方依赖细节;
但上面的几个项目,基本都是围绕着 class 加密(除了GraalVM),这无法实现我们的第二个需求。
设计目标:
- 将项目三方依赖 jar 进行加密,使其无法使用 jadx 反编译,但运行时会生成解密后的临时文件。
- 将项目本身的 class 进行加密,使其无法使用 jadx 反编译运行时解密后的文件。
- 加密策略要灵活,轻量,对启动速度,包体积,内存消耗,接口性能的影响要控制在 5% 以内;
设计方案:
- 加密jar时,使用 maven 打包工具,repackage fat jar;将其内部 lib 目录的依赖进行加密;使 jadx 无法反编译;
- 加密class时,对于核心业务代码,使用 javassist 工具将其重写,清空方法体,重置属性值;
- 解密jar时,将指定目录的 加密包 解密 到指定目录,并将其放入 springboot classloader classpath 里。
- 解密class时,agent 配合判断是否是加密 class,如果是,则寻找加密 class 文件,找到后解密,返回解密后的 classBytes。
逻辑如下:
注意点:
- javassist 重写方法体时,需要将 lib 里的所有代码都加入 classpool 的 classpath 里。
- javassist 加密后的类,需将其放入到当前 lib 的单独目录进行个例,防止类冲突。
- agent 解密要轻量,不能影响程序性能;
- 三方包的加解密重新打包后,jar 顺序发生变化,较小可能会导致类冲突(比如 log4j)。需要在测试环境验证,如果存在冲突,则需要排包。
通过以上方案,我们实现了一个极其轻量的 maven 加密,agent 解密插件。他能够将三方包彻底加密,使 jadx 等工具无法反编译 ,屏蔽我们的三方依赖细节,同时,该插件也可以加密我们的业务 class 代码,使 jadx 无法反编译运行时生成的代码,从而一定程度的保护我们的知识产权;
另外,私有的加密算法,在性能,体积,内存等方便的影响都控制在 5% 以内。
为了防止混淆后的代码影响 arthas 的使用和 bug patch 的应用,我们放弃了混淆方案,只能说是一种权衡与取舍吧。
从软件防破解的角度来理解,通常只能是加大破解的难度,铁了心想要破解的话,就算是 ProGuard 混淆,也无法解决。也许只能用 GraalVM,但不是每一个客户都会用这个。
[1]ProGuard: https://github.com/Guardsquare/proguard
[2]jar-protect: https://gitee.com/chejiangyi/jar-protect
[3]GraalVM: https://javakk.com/tag/graalvm
[4]core-lib/xjar: https://github.com/core-lib/xjar
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。