使用springboot防止反编译proguard+xjar
作者:mawei7510
一、背景
项目组核心代码模块部署于用户服务器上,直接甩jar包到服务器的方式,极有可能导致数据泄露和代码泄露,为了防止有技术能力的用户反编译我们的程序,采用了proguard和xjar两种方式来混淆和加密jar包,注:加密技术只是提高别人获取你的代码的门槛,没有绝对安全的加密方式,而安全等级越高,程序开发、运维、部署的成本就越高,所以,合适的加密技术就是最好的。
二、简介
1. ProGuard是一个压缩、优化和混淆Java字节码文件的免费的工具
它可以删除无用的类、字段、方法和属性。可以删除没用的注释,最大限度地优化字节码文件。
它还可以使用简短的无意义的名称来重命名已经存在的类、字段、方法和属性。常常用于Android开发用于混淆最终的项目,增加项目被反编译的难度。
2. Xjar
- Spring Boot JAR 安全加密运行工具, 同时支持的原生JAR
- 基于对JAR包内资源的加密以及拓展ClassLoader来构建的一套程序加密启动, 动态解密运行的方案, 避免源码泄露以及反编译.
功能特性:
- 无代码侵入, 只需要把编译好的JAR包通过工具加密即可.
- 完全内存解密, 降低源码以及字节码泄露或反编译的风险.
- 支持所有JDK内置加解密算法.
- 可选择需要加解密的字节码或其他资源文件.
- 支持Maven插件, 加密更加便捷.
- 动态生成Go启动器, 保护密码不泄露.
3.ClassFinal是一款Java class文件安全加密工具
支持直接加密jar包或war包,无需修改任何项目代码,兼容spring-framework,可避免源码泄漏或字节码被反编译,
功能特性:
- 无需修改原项目代码,只要把编译好的jar/war包用本工具加密即可。
- 运行加密项目时,无需求修改tomcat,spring等源代码。
- 支持普通jar包、springboot jar包以及普通java web项目编译的war包。
- 支持spring framework、swagger等需要在启动过程中扫描注解或生成字节码的框架。
- 支持maven插件,添加插件后在打包过程中自动加密。
- 支持加密WEB-INF/lib或BOOT-INF/lib下的依赖jar包。
三、预研了classfinal
classfinal其实也可以起到代码加密的效果,功能也很强大,被classfinal加密过后的jar包,反编译了以后,方法返回值会return null或者0,方法内部会自动去掉。
并且用classfinal加密过后的jar包启动方式需要用javaagnet启动。
而相比较xjar,反编译以后,反编译后,直接显示Internal Error.
1.module pom文件引入
<plugin> <groupId>net.roseboy</groupId> <artifactId>classfinal-maven-plugin</artifactId> <version>1.2.1</version> <configuration> <password>#</password><!-- #表示启动时不需要密码,事实上对于代码混淆来说,这个密码没什么用,它只是一个启动密码 --> <packages>com.nick.gnss</packages><!-- 加密的包名,多个包用逗号分开--> <excludes>org.spring</excludes> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>classFinal</goal> </goals> </execution> </executions> </plugin>
2.启动方式
1)无密码启动
java -jar gnss-server-1.0.0-encrypted.jar
2)有密码启动
java -javaagent:gnss-server-1.0.0-encrypted.jar="-pwd 123456" -jar gnss-server-1.0.0-encrypted.jar
3.反编译后的效果
所有的方法,return 0 或者 null. 方法体内部是空的。
四、引入proguard混淆
1.module中增加proguard.cfg文件
#指定Java的版本 -target 1.8 #proguard会对代码进行优化压缩,他会删除从未使用的类或者类成员变量等 -dontshrink #是否关闭字节码级别的优化,如果不开启则设置如下配置 -dontoptimize #混淆时不生成大小写混合的类名,默认是可以大小写混合 -dontusemixedcaseclassnames # 对于类成员的命名的混淆采取唯一策略 -useuniqueclassmembernames #混淆时不生成大小写混合的类名,默认是可以大小写混合 -dontusemixedcaseclassnames #混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代 -adaptclassstrings #对异常、注解信息予以保留 -keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod # 此选项将保存接口中的所有原始名称(不混淆)--> -keepnames interface ** { *; } # 此选项将保存所有软件包中的所有原始接口文件(不进行混淆) #-keep interface * extends * { *; } #保留参数名,因为控制器,或者Mybatis等接口的参数如果混淆会导致无法接受参数,xml文件找不到参数 -keepparameternames # 保留枚举成员及方法 -keepclassmembers enum * { *; } # 不混淆所有的set/get方法 -keepclassmembers public class * {void set*(***);*** get*();} # 不混淆所有包含Component等注解的类 -keep @org.springframework.context.annotation.Bean class * {*;} -keep @org.springframework.context.beans.factory.annotation.Autowired class * {*;} -keep @org.springframework.context.beans.factory.annotation.Value class * {*;} -keep @org.springframework.stereotype.Service class * {*;} -keep @org.springframework.stereotype.Component class * {*;} -keep @org.springframework.web.bind.annotation.RestController class * {*;} -keep @org.springframework.context.annotation.Configuration class * {*;} #忽略warn消息 -ignorewarnings #忽略note消息 -dontnote #打印配置信息 -printconfiguration #启动类不需要混淆 -keep class com.nick.GnssApplication { public static void main(java.lang.String[]); }
2.module pom文件引入
此处需要注意,proguard plugin需要放在repackage plugin之前,否则混淆没有效果。原理就是在打包之前将代码混淆,然后再打包。
<!--代码混淆proguard--> <plugin> <groupId>com.github.wvengen</groupId> <artifactId>proguard-maven-plugin</artifactId> <version>2.6.0</version> <executions> <!-- 以下配置说明执行mvn的package命令时候,会执行proguard--> <execution> <phase>package</phase> <goals> <goal>proguard</goal> </goals> </execution> </executions> <configuration> <!-- 就是输入Jar的名称,我们要知道,代码混淆其实是将一个原始的jar,生成一个混淆后的jar,那么就会有输入输出。 --> <injar>${project.build.finalName}.jar</injar> <!-- 输出jar名称,输入输出jar同名的时候就是覆盖,也是比较常用的配置。 --> <outjar>${project.build.finalName}.jar</outjar> <!-- 是否混淆 默认是true --> <obfuscate>true</obfuscate> <!-- 配置一个文件,通常叫做proguard.cfg,该文件主要是配置options选项,也就是说使用proguard.cfg那么options下的所有内容都可以移到proguard.cfg中 --> <proguardInclude>${project.basedir}/proguard.cfg</proguardInclude> <!-- 额外的jar包,通常是项目编译所需要的jar --> <libs> <lib>${java.home}/lib/rt.jar</lib> <lib>${java.home}/lib/jce.jar</lib> <lib>${java.home}/lib/jsse.jar</lib> </libs> <!-- 对输入jar进行过滤比如,如下配置就是对META-INFO文件不处理。 --> <inLibsFilter>!META-INF/**,!META-INF/versions/9/**.class</inLibsFilter> <!-- 这是输出路径配置,但是要注意这个路径必须要包括injar标签填写的jar --> <outputDirectory>${project.basedir}/target</outputDirectory> <!--这里特别重要,此处主要是配置混淆的一些细节选项,比如哪些类不需要混淆,哪些需要混淆--> <options> <!-- 可以在此处写option标签配置,不过我上面使用了proguardInclude,故而我更喜欢在proguard.cfg中配置 --> </options> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.7.RELEASE</version> <configuration> <mainClass>com.nick.GnssApplication</mainClass> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin>
3.反编译看效果
查看xxx.jar是否有混淆的效果,而不是xxx_proguard_base.jar(此jar是没有混淆的原jar)
效果杠杠滴!!!需要试运行,并且注意配置proguard,否则会导致程序运行异常,这可能就是proguard的唯一缺点了吧,欢迎拍砖。。。
4.运行查看效果
启动成功,没毛病
五、引入xjar
1.parent pom文件引入
重点在最下面的xjar plugin
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.nick</groupId> <artifactId>nick-server</artifactId> <version>1.0.0</version> <name>nick-server</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.3.7.RELEASE</spring-boot.version> </properties> <modules> <module>gnss-server</module> </modules> <packaging>pom</packaging> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!--代码加密--> <plugin> <groupId>com.github.core-lib</groupId> <artifactId>xjar-maven-plugin</artifactId> <version>v2.0.7</version> <executions> <execution> <goals> <goal>build</goal> </goals> <phase>package</phase> <configuration> <password>44889951235894612351265ABD123</password> <mode>1</mode> <sourceDir>${project.build.directory}</sourceDir> <targetJar>${project.build.finalName}_x.jar</targetJar> <includes> <include>com/nick/**</include> </includes> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
2. module pom文件引入
需要放在module pom文件plugin 最后一个,保证xjar是最后一个执行plugin
<!--代码加密xjar--> <plugin> <groupId>com.github.core-lib</groupId> <artifactId>xjar-maven-plugin</artifactId> </plugin>
3.编译打包
4.反编译看效果
效果杠杠滴
5.运行查看效果
六、proguard + xjar
将第四步和第五步融合即可,但是要注意pom文件中的plugin的先后问题,不然要么混淆失败,要么加密失败。
1.完整版的parent pom文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.nick</groupId> <artifactId>nick-server</artifactId> <version>1.0.0</version> <name>nick-server</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.3.7.RELEASE</spring-boot.version> </properties> <modules> <module>gnss-server</module> </modules> <packaging>pom</packaging> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <!--代码加密--> <plugin> <groupId>com.github.core-lib</groupId> <artifactId>xjar-maven-plugin</artifactId> <version>v2.0.7</version> <executions> <execution> <goals> <goal>build</goal> </goals> <phase>package</phase> <configuration> <password>44889951235894612351265ABD123</password> <mode>1</mode> <sourceDir>${project.build.directory}</sourceDir> <targetJar>${project.build.finalName}_x.jar</targetJar> <includes> <include>com/nick/**</include> </includes> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
2.完整版的poguard.cfg
#指定Java的版本 -target 1.8 #proguard会对代码进行优化压缩,他会删除从未使用的类或者类成员变量等 -dontshrink #是否关闭字节码级别的优化,如果不开启则设置如下配置 -dontoptimize #混淆时不生成大小写混合的类名,默认是可以大小写混合 -dontusemixedcaseclassnames # 对于类成员的命名的混淆采取唯一策略 -useuniqueclassmembernames #混淆时不生成大小写混合的类名,默认是可以大小写混合 -dontusemixedcaseclassnames #混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代 -adaptclassstrings #对异常、注解信息予以保留 -keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod # 此选项将保存接口中的所有原始名称(不混淆)--> -keepnames interface ** { *; } # 此选项将保存所有软件包中的所有原始接口文件(不进行混淆) #-keep interface * extends * { *; } #保留参数名,因为控制器,或者Mybatis等接口的参数如果混淆会导致无法接受参数,xml文件找不到参数 -keepparameternames # 保留枚举成员及方法 -keepclassmembers enum * { *; } # 不混淆所有的set/get方法 -keepclassmembers public class * {void set*(***);*** get*();} # 不混淆所有包含Component等注解的类 -keep @org.springframework.context.annotation.Bean class * {*;} -keep @org.springframework.context.beans.factory.annotation.Autowired class * {*;} -keep @org.springframework.context.beans.factory.annotation.Value class * {*;} -keep @org.springframework.stereotype.Service class * {*;} -keep @org.springframework.stereotype.Component class * {*;} -keep @org.springframework.web.bind.annotation.RestController class * {*;} -keep @org.springframework.context.annotation.Configuration class * {*;} #忽略warn消息 -ignorewarnings #忽略note消息 -dontnote #打印配置信息 -printconfiguration #启动类不需要混淆 -keep class com.nick.GnssApplication { public static void main(java.lang.String[]); }
3.完整版的module pom文件
该pom文件中注意两点
- 1)引入parent节点
- 2)需要放在最后一个plugin执行
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>nick-server</artifactId> <groupId>com.nick</groupId> <version>1.0.0</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.nick</groupId> <artifactId>gnss-server</artifactId> <version>1.0.0</version> <name>gnss-server</name> <description>gnss-server</description> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.3.7.RELEASE</spring-boot.version> <!--maven.build.timestamp保存了maven编译时间戳--> <!--在Maven 3.2.2+中, maven.build.timestamp已被重新定义,显示UTC中的时间,比中国时间慢8个小时--> <timestamp>${maven.build.timestamp}</timestamp> <!--指定时间格式--> <maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <!--1.2.80以下存在安全漏洞--> <!--<version>1.2.78</version>--> <version>1.2.83</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>30.1.1-jre</version> </dependency> <!--nacos-web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <configuration> <delimiters> <delimiter>@</delimiter> </delimiters> <useDefaultDelimiters>false</useDefaultDelimiters> </configuration> </plugin> <!--代码混淆proguard--> <plugin> <groupId>com.github.wvengen</groupId> <artifactId>proguard-maven-plugin</artifactId> <version>2.6.0</version> <executions> <!-- 以下配置说明执行mvn的package命令时候,会执行proguard--> <execution> <phase>package</phase> <goals> <goal>proguard</goal> </goals> </execution> </executions> <configuration> <!-- 就是输入Jar的名称,我们要知道,代码混淆其实是将一个原始的jar,生成一个混淆后的jar,那么就会有输入输出。 --> <injar>${project.build.finalName}.jar</injar> <!-- 输出jar名称,输入输出jar同名的时候就是覆盖,也是比较常用的配置。 --> <outjar>${project.build.finalName}.jar</outjar> <!-- 是否混淆 默认是true --> <obfuscate>true</obfuscate> <!-- 配置一个文件,通常叫做proguard.cfg,该文件主要是配置options选项,也就是说使用proguard.cfg那么options下的所有内容都可以移到proguard.cfg中 --> <proguardInclude>${project.basedir}/proguard.cfg</proguardInclude> <!-- 额外的jar包,通常是项目编译所需要的jar --> <libs> <lib>${java.home}/lib/rt.jar</lib> <lib>${java.home}/lib/jce.jar</lib> <lib>${java.home}/lib/jsse.jar</lib> </libs> <!-- 对输入jar进行过滤比如,如下配置就是对META-INFO文件不处理。 --> <inLibsFilter>!META-INF/**,!META-INF/versions/9/**.class</inLibsFilter> <!-- 这是输出路径配置,但是要注意这个路径必须要包括injar标签填写的jar --> <outputDirectory>${project.basedir}/target</outputDirectory> <!--这里特别重要,此处主要是配置混淆的一些细节选项,比如哪些类不需要混淆,哪些需要混淆--> <options> <!-- 可以在此处写option标签配置,不过我上面使用了proguardInclude,故而我更喜欢在proguard.cfg中配置 --> </options> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.7.RELEASE</version> <configuration> <mainClass>com.nick.GnssApplication</mainClass> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <!--代码加密xjar--> <plugin> <groupId>com.github.core-lib</groupId> <artifactId>xjar-maven-plugin</artifactId> </plugin> <!--classfinal加密程序--> <!--<plugin>--> <!--<groupId>net.roseboy</groupId>--> <!--<artifactId>classfinal-maven-plugin</artifactId>--> <!--<version>1.2.1</version>--> <!--<configuration>--> <!--<password>#</password><!– #表示启动时不需要密码,事实上对于代码混淆来说,这个密码没什么用,它只是一个启动密码 –>--> <!--<packages>com.nick.gnss</packages><!– 加密的包名,多个包用逗号分开–>--> <!--<excludes>org.spring</excludes>--> <!--</configuration>--> <!--<executions>--> <!--<execution>--> <!--<phase>package</phase>--> <!--<goals>--> <!--<goal>classFinal</goal>--> <!--</goals>--> <!--</execution>--> <!--</executions>--> <!--</plugin>--> </plugins> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> </build> </project>
4.打包编译
5.反编译查看效果
OK, 效果杠杠滴,混淆+加密。
至此,混淆+加密搞定。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。