前不久被要求对java web的war包做代码混淆,我使用proguard6.0.3完成的。后面也许还会用到,因此记录下过程和配置方法,demo代码按照图1结构进行组织。仅对混淆 功能进行说明,其他功能如压缩、优化不考虑。
混淆需求 混淆代码的需求如下:
混淆包名、类名、成员属性、方法名等。
不混淆涉及到dao
命名空间配置、aop切点的类名或方法名。
不混淆注解。
不混淆proguard默认配置的类名或方法名。
本Demo中按照需求表现为:
混淆controller
和service
中的包名、类名、成员属性、方法名。
假设aop切面涉及到controller
包名,因此不混淆controller
包名;假设aop切面涉及到service.findOne()
方法,因此不混淆service
类的findOne()
方法;dao
中所有类涉及到mybatis命名空间,因此不混淆dao
中所有类。
不能混淆注解,如@RestController
,@Service
等,否则spring无法正确解析。
proguard默认配置了不混淆enum
、native
方法等,保留这些默认配置。
图1 Demo代码包结构
PROGUARD配置实践 proguard的配置可以先使用GUI版程序进行配置,然后将配置复制出来保存为.pro
文件,再使用命令行执行混淆操作(命令行执行混淆比GUI更快)。
图2~图4为GUI界面的混淆配置:
图2 PROGUARD混淆界面
图2中显示了混淆的基本配置:
Obfuscates
表示开启混淆。
Print mapping
表示混淆的映射保存到的文件,一定要保存,否则后期排查无法找到混淆前的名字。
Keep package names
表示不混淆的包名。
Keep attributes
表示不混淆的属性,其中包含Annotation
。
Native method names
表示不混淆native
方法
Keep additional class names and class member names
表示不混淆类名或成员名,用于配置具体的不混淆的类、属性或方法,见图3、图4。
图3为类名的混淆配置,即配置不混淆的类(keep表示保留,即不混淆)。Class
栏填写表示不混淆的类,现需求为只对controller
和service
进行混淆,也就是除了这2个包之外所有的包都不混淆,所以是keep!controller
且!service
包,那么配置将为图3中所示。
图3 类名混淆配置
根据测试,发现proguard的设定为:如果只设置Class
栏,而不设置Class member
栏,那么只会保留其他包和类的包名和类名,而成员属性和方法依然会被混淆,因此必须指定所有的属性和方法名,即图3中所示(也可为*
)。
图4为方法名(或成员属性)的混淆配置,即配置不混淆的方法名,按图配置即可,Keep
栏需要选择Keep class members only
。
图4 方法名混淆配置
混淆的配置暂时只用到上述配置,其余配置含义可以通过查阅proguard文档获知。
另外,还有一些额外的附加配置,比如打印参数,JDK版本以及jar包问题,图5为额外配置。
图5 额外配置
Preverify
勾选表示开启预检查,建议勾选,否则可能混淆出来的jar或class无法使用。
Target
选择对应版本,选错了好像也没有发现有什么异样。
Note potential mistakes in the configuration
去掉勾选表示不打印NOTE
信息,建议去掉勾选,否则控制台会打印很多没必要查看的信息,并且会降低混淆执行速度。
Warn about possibly erroneous input
勾选表示打印WARN
信息,建议勾选,如果有错误可以查看。
Ignore warnings about possibly erroneous input
勾选表示忽略warn信息,建议勾选,否则会因为warn而无法混淆,很多时候warn只作参考,并不会对混淆结果造成影响。
Skip non-public library class members
默认会勾选,据提示信息可以得知可以执行更快速,但如果有需求也可以去掉勾选。
Keep directories
勾选表示混淆后的jar包保持原有的目录,如果混淆了包名,那么这些包名原始的目录或者其中的目录都将是空文件夹,建议勾选,否则有可能运行混淆的代码找不到jar包中的class。
配置完毕后,到proguard左侧的Process
里,执行下方的View configuration
可以得到配置脚本,脚本见文末案列.pro配置文件及注释
。
混淆WAR包实践 从图1中可以看到,common
、repository
、service
模块是单独的模块,分别包含各自模块或MVC架构层的代码,web
模块是webapp所在的模块,因此当项目打war包后会出现图6所示的包结构,前3者的包会以jar包的形式出现在lib
目录中,而web
模块中的controller
代码将在war包的classes中。
图6 打war包后的包结构
此时如果按照这样的包结构直接将war包执行混淆,那么混淆后的war包会出现web
模块的controller
代码无法混淆的情况,并且如果没有设置指定keepdirectories
,那么controller
等代码还会被删除掉。
出现上述问题的原因是proguard只支持混淆jar包和存放在包名层级目录的父目录下的class文件 。jar包很好理解,关键是class文件的混淆。假设class为p1.p2.C1
,那么对应的包名层级目录为p1/p2/C1.class
,此时如果要混淆C1.class
,那么需要上述包名层级目录有父目录,假设为classes
,即目录层级为classes/p1/p2/C1.class
,然后proguard配置路径为classes
才可行。
备注:如果proguard配置路径为class文件,即C1.class
则会得到warn提示Warning: class [C1.class] unexpectedly contains class [p1.p2.C1]
而导致无法混淆;如果配置路径不是classess
而是p1
,那么将得到warn提示Warning: class [p2/C1.class] unexpectedly contains class [p1.p2.C1]
。看到这里应该能明白为什么要配置为父目录classes
了吧。
再回过来看看图6,war包根目录下的文件夹是WEB-INF
,然后才是class层级目录的父目录classes
,根据上述描述,proguard只支持混淆存放在包名层级目录的父目录下的class文件 ,因此如果直接对war执行混淆,controller
的代码会多出一层目录,导致无法混淆。
如果要执行混淆,办法有2个:
1、将web
包下的所有class代码移到单独的模块,与common
等这种模块类似
2、war包拆开后对jar包和class文件分别混淆,再合并打成war包
方法1肯定更方便,但是由于项目已经按照图6的结构完成了,如果贸然改变包结构可能会有风险,而且其他项目也是按照这种结构架设的,如果都去更改模块结构会特别麻烦。因此考虑采用方法2,这种方式虽然看起来麻烦,但可以编写脚本来完成拆包和打包任务,最重要的是其他项目也是可以复用该脚本的。
拆分war包需要将war包解压,然后对jar包和WEB-INF/classes
下的class文件分别混淆,再将混淆后的文件合并回解压文件夹,最后再重新打成war包即可。具体方法不赘述,仅列出war包的解压、打包方法、混淆命令行语句。需要注意的是合并混淆文件之前要先删除WEB-INF/classes
下的class文件,因为混淆后包名会改变,不会覆盖原先的文件,而jar包混淆后文件名不变,是可以覆盖原先的jar包的。
1 2 3 4 5 6 7 8 # 解压,只能解压到当前目录 jar -xf proj.war # 打包,将proj下面的所有文件打包到proj-obfuscate.war包中 jar -cf proj-obfuscate.war -C proj/ . # proguard按照config.pro执行 java -jar proguard.jar @config.pro
执行完成,可以得到混淆后的war包,包结构如图7所示。
图7 混淆后的war包结构
案例代码 1. .pro
配置文件及注释 1 2 3 4 5 6 7 # 输入输出,分别配置classes下的class文件和jar包混淆的输入输出 # 括号中为过滤的文件,即不查询的文件,如果混淆过程中报错文件找不到,可以将该文件过滤掉 -injars temp-orgin\WEB-INF\classes(!org/aspectj/org/eclipse/jdt/internal/compiler/parser/UpdateParserFiles.class) -outjars temp-obfuscated\WEB-INF\classes -injars temp-orgin\WEB-INF\lib\service-6.0.3.jar(!org/aspectj/org/eclipse/jdt/internal/compiler/parser/UpdateParserFiles.class) -outjars temp-obfuscated\WEB-INF\lib\service-6.0.3.jar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # 混淆配置 -target 1.8 -libraryjars C:\Program Files\Java\jdk1.8.0_40\jre\lib\rt.jar -dontnote -ignorewarnings -dontshrink -dontoptimize -keepdirectories -printmapping 'mapping.txt' # keep配置 -keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod # controller包名不混淆,否则aop无法切入 -keeppackagenames online.dinghuiye.controller # Obfuscate controller and service -keep class !online.dinghuiye.controller.**,!online.dinghuiye.service.** { <fields>; <methods>; } # 不混淆指定方法名的方法 -keepclassmembers class * { *** findOne(...); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 保留的proguard的默认配置 # Also keep - Enumerations. Keep the special static methods that are required in # enumeration classes. -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } # Also keep - Database drivers. Keep all implementations of java.sql.Driver. -keep class * extends java.sql.Driver # Also keep - Swing UI L&F. Keep all extensions of javax.swing.plaf.ComponentUI, # along with the special 'createUI' method. -keep class * extends javax.swing.plaf.ComponentUI { public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent); } # Keep - Native method names. Keep all native class/method names. -keepclasseswithmembers,includedescriptorclasses class * { native <methods>; }
2. 混淆前后代码对比
SomeUtil
没有混淆
SomeDao
没有混淆
SomeService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package online.dinghuiye.service;import java.util.List;public interface SomeService { Object findOne () ; List<Object> findList () ; } package a.a.a;import java.util.List;public abstract interface a { public abstract Object findOne () ; public abstract List<Object> a () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package online.dinghuiye.service.impl;import online.dinghuiye.repository.dao.SomeDao;import online.dinghuiye.service.SomeService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;@Service public class SomeServiceImpl implements SomeService { @Autowired private SomeDao dao; @Override public Object findOne () { List<Object> list = dao.findList(); if (list.size() > 0 ) { return list.get(0 ); } return null ; } @Override public List<Object> findList () { return dao.findList(); } } package a.a.a.a;import java.util.List;import online.dinghuiye.repository.dao.SomeDao;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class a implements a .a .a .a { @Autowired private SomeDao a; public Object findOne () { List<Object> list = this .a.findList(); if (list.size() > 0 ) { return list.get(0 ); } return null ; } public List<Object> a () { return this .a.findList(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package online.dinghuiye.controller;import online.dinghuiye.service.SomeService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/") public class SomeController { @Autowired private SomeService service; @RequestMapping("list") public Object findList () { return service.findList(); } } package online.dinghuiye.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping({"/"}) public class a { @Autowired private a.a.a.a a; @RequestMapping({"list"}) public Object a () { return this .a.a(); } }
3. mapping.txt 1 2 3 4 5 6 7 8 9 10 11 12 online.dinghuiye.controller.SomeController -> online.dinghuiye.controller.a: online.dinghuiye.service.SomeService service -> a 13:13:void <init>() -> <init> 20:20:java.lang.Object findList() -> a online.dinghuiye.service.SomeService -> a.a.a.a: java.lang.Object findOne() -> findOne java.util.List findList() -> a online.dinghuiye.service.impl.SomeServiceImpl -> a.a.a.a.a: online.dinghuiye.repository.dao.SomeDao dao -> a 14:14:void <init>() -> <init> 21:25:java.lang.Object findOne() -> findOne 30:30:java.util.List findList() -> a
参考文档
封面图来源:https://www.cnblogs.com/lmq3321/p/10320671.html