维基百科对ProGuard的介绍是:ProGuard是一个压缩(shrink),优化(optimize)与混淆(Obfuscate)Java代码的开源命令行工具。也就是说混淆
只是ProGuard的其中一个功能,本文也只介绍它的混淆功能。
About ProGuard
ProGuard能通过重命名类名,字段名,方法名为一些没有意义的名字来混淆Java和Android程序,从而使逆向工程更难。并且ProGuard已经是Android SDK的一部分了,当然本文只讲如何混淆Java代码。
用法
本文介绍两种方式:使用UI工具 和 Maven集成。
- UI工具
这个比较简单,先从https://sourceforge.net/projects/proguard/下载最新版的ProGuard,然后解压,到bin目录下找到proguardgui.bat
双击即可,程序运行后的界面如下:
ProGuard
在Input/Output中,Add input选择要混淆的JAR包(ProGuard不支持混淆单个class文件,但是我们可以通过命令把一个或多个class打成jar包:jar cvf Test.jar com/afei/test/*.class
),Add output指定输出文件名。Obfuscation中有很多混淆自定义规则,根据需求自行调整,Shrinking和Optimization视情况而定是否需要勾选(如果只是混淆代码,则建议关闭)。最后选择Process,点击右下角的Process!即可。如下图所示,User.java混淆前后对比,我们发现类名,属性名都被重命名为一些毫无意义的命名了:
image.png
- Maven集成
通过maven集成方式从而在maven打包阶段就达到了混淆的目。集成方式非常简单,只需修改POM,添加如下一些代码即可:
-- 添加依赖
<dependency>
<groupId>net.sf.proguardgroupId>
<artifactId>proguard-baseartifactId>
<version>5.3.3version>
dependency>
-- 添加插件
<plugin>
<groupId>com.github.wvengengroupId>
<artifactId>proguard-maven-pluginartifactId>
<version>2.0.15-SNAPSHOTversion>
<executions>
<execution>
<phase>packagephase>
<goals><goal>proguardgoal>goals>
execution>
executions>
<configuration>
<proguardVersion>5.3.3proguardVersion>
<injar>${project.build.finalName}.jarinjar>
<outjar>${project.build.finalName}.jaroutjar>
<obfuscate>trueobfuscate>
<libs>
<lib>${java.home}/lib/rt.jarlib>
<lib>${java.home}/lib/jce.jarlib>
libs>
<options>
<option>-dontshrinkoption>
<option>-dontoptimizeoption>
<option>-keepnames interface **option>
<option>-keepparameternamesoption>
<option>-keeppackagenamesoption>
options>
configuration>
plugin>
集成后执行maven package
打包,就会看到target目录下会多出一些命名中带有proguard的文件,例如afei-demo-1.0.0-SNAPSHOT_proguard_base.jar
,proguard_map.txt
,proguard_seed.txt
等,并且每个目录下的类名都被重命名为a, b, c等毫无意义的类名:
confusion target
- 配置说明
maven集成ProGuard后,可自定义的地方主要集中在options标签中,一些option说明如下:
-dontshrink
shrink是ProGuard几大核心功能之一,配置了dontshrink表示不需要压缩,即关闭shrink功能
-dontoptimize
optimize也是ProGuard几大核心功能之一,配置了dontoptimize表示不需要优化,即关闭soptimize功能
-keeppackagenames
是否不混淆包名,如果配置了该项,那么包名会完整保留。如果没有配置,那么除了根目录(com.afei.test),子目录(例如bean, controller, impl等)都会被混淆。
-keepattributes SourceFile,LineNumberTable
抛出异常时保留代码行号,在异常分析中可以方便定位
-dontusemixedcaseclassnames
这个是给Windows系统用户的,因为ProGuard假定使用的操作系统是能区分大小写名,但是Windows不是这样的操作系统,所以必须为ProGuard指定-dontusemixedcaseclassnames选项
-keep class com.afei.test.Application
Application这个类名不混淆,但是方法名,属性名,以及方法中的内容还是会混淆;
-keep class com.afei.test.Application { *; }
Application这个类的类名,方法名,属性名完全保留不做任何混淆,但是方法体内的内容还是会混淆。
-keepnames interface **
所有接口类命名不做任何混淆,但是接口里的属性和方法会还是会被混淆;
-keepnames enum **
枚举类名不做任何混淆,但是枚举的值会混淆;
-keep public class * implements java.io.Serializable{
public *;
protected *;
private *;
}
保留实现了Serializable接口类中的公有的,友好的,私有的成员(属性和方法),这个配置主要是对应实体类的配置。
-keep class com.afei.test.dao.** { *; }
dao层的类名和方法都不混淆;
-keepnames
用法如下,保留Application的类名和main方法不混淆:
-keepnames public class com.afei.test.Application {public static void main(java.lang.String[]);
}
用法如下,保留FtpConfigSaveJob的类名和execute方法不混淆:
-keepclasseswithmembers public class com.afei.test.job.FtpConfigSaveJob {
com.xxl.job.core.biz.model.ReturnT execute(java.lang.String);
}
配置中符号的含义:
!:感叹号表示除这个以外,例如-keep class !com.afei.test.Application表示出了Application.java,其他的类名都不混淆
遇到的问题
通过使用ProGuard混淆springboot项目的过程,笔者遇到了下面这些问题:
- 工程目录要清晰,比如VO, DTO, PO存放目录要统一,自定义Exception子类存放目录等。因为这些类名,甚至属性名都不能混淆,VO属性名与接口定义有关,PO与Mapper.xml有关;
- 与序列化反序列相关的对象也不会混淆,与第三方约定的接口相关的模型也不能混淆等;
- xxl-job相关类不能混淆,方法申明必须是execute(String),笔者相信还会有其他的框架也会有类似的约定和限制;
- springboot的Application.java也不能混淆,因为main方法不能被重命名,Application.java类名最好也不要被重命名,因为maven中有配;置
com.afei.test.Application
; - 很多注解不能混淆
- … …
如下图所示,是默认配置混淆后的Application.java,很明显,这样的springboot工程是无法启动的,因为main主方法被混淆为a了:
混淆后的Application.java
另外,由于ProGuard默认会将每个子目录下的类都重命名为a, b, c。例如com.afei.test.a下的类会被重命名为a.class,b.class,c.class,而com.afei.test.b下的类也会被重命名为a.class,b.class,c.class。这样就会导致spring初始化加载bean时抛出下面的异常:
Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'b' for bean class [com.afei.test.config.b] conflicts with existing, non-compatible bean definition of same name and class [com.afei.test.common.b]
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate(ClassPathBeanDefinitionScanner.java:345)
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:283)
at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:135)
at
ProGuard总结
通过上面的分析可知,ProGuard或者类似原理的代码混淆工具的限制太多太多,对工程目录有严格的要求,而且很多地方需要明确配置不允许混淆,以后有新增的代码,还需要评估是否需要申明不需要混淆。这样分析下来后发现,如果想要使用ProGuard混淆服务端的代码,最后几乎只能混淆方法体内的内容。
XJar
针对ProGuard或者同原理方案的缺陷,笔者找到了另一种方案:XJar。
官方介绍XJar是Spring Boot JAR 安全加密运行工具,同时支持的原生JAR。基于对JAR包内资源的加密以及拓展ClassLoader来构建的一套程序加密启动,动态解密运行的方案,避免源码泄露或反编译。功能特性如下:
- 无需侵入代码,只需要把编译好的JAR包通过工具加密即可。
- 完全内存解密,杜绝源码以及字节码泄露或反编译。
- 支持所有JDK内置加解密算法。
- 可选择需要加解密的字节码或其他资源文件,避免计算资源浪费。
详细的介绍和用法请参考XJar GitHub主页: https://github.com/core-lib/xjar,介绍的非常详细。