一、背景
项目组核心代码模块部署于用户服务器上,直接甩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, 效果杠杠滴,混淆+加密。
至此,混淆+加密搞定。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论