前不久被要求对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.** {
;
;
}
# 不混淆指定方法名的方法
-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 ;
}
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 findList();
}
// 混淆后
package a.a.a;
import java.util.List;
public abstract interface a
{
public abstract Object findOne();
public abstract List a();
}
SomeServiceImpl
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 list = dao.findList();
if (list.size() > 0) {
return list.get(0);
}
return null;
}
@Override
public List 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 list = this.a.findList();
if (list.size() > 0) {
return list.get(0);
}
return null;
}
public List a()
{
return this.a.findList();
}
}
SomeController
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.txt1
2
3
4
5
6
7
8
9
10
11
12online.dinghuiye.controller.SomeController -> online.dinghuiye.controller.a:
online.dinghuiye.service.SomeService service -> a
13:13:void () ->
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 () ->
21:25:java.lang.Object findOne() -> findOne
30:30:java.util.List findList() -> a
参考文档