前端代码因为需要直接传输到客户端执行,因此代码混淆技术较早的开始发展,当前比较成熟。后端代码长期以来混淆的需求并不突出,然而随着 Java 代码需要被客户接触到,并不放在公司完全受控的环境,如以 apk 形式在用户手机上或以应用形式在专有云中,因此后端代码混淆提到了日程中。
成熟的 Java 混淆工具很多,如下表:
名称 | 授权 | 主页 |
---|---|---|
yGuard | LGPL | http://www.yworks.com/products/yguard |
ProGuard | GPLv2 | https://www.guardsquare.com/en/proguard |
Facebook ProGuard 分支 | GPLv2 | https://github.com/facebook/proguard |
DashO | Commercial | https://www.preemptive.com/products/dasho |
Allatori | Commercial | http://www.allatori.com |
Stringer | Commercial | https://jfxstore.com |
Java Antidecompiler | Commercial | http://www.bisguard.com/help/java/ |
Zelix KlassMaster | Commercial | http://www.zelix.com |
也有不少工具因为长期未更新直接不在考虑范围内,如 Jode (LGPL、最后更新:2002 年)、 JavaGuard (LGPLv2,最后更新:2002 年)、 jarg (开源,最后更新:2003 年)。
一般初步学习适用从开源免费的软件开始,那么我们就从 yGuard 和 ProGuard 两者来比较,首先看 Google 搜索:
很显然 ProGuard 更加活跃。从混淆情况看,既然是混淆工具,混淆上差别不大,yGuard 基于 Ant Task,因此在 maven 中需要 maven-antrun-plugins 来支持,并且需要写 ant task 脚本。ProGuard 有 proguard-maven-plugin + 配置文件的形式,更加方便。同时 ProGuard 有 Facebook ProGuard 的 Folk 版本,和 DexGuard 商业版本两个较活跃的衍生版本,支持整个生态良好发展。因此我们选择 ProGuard。
因为我们的应用主要是面向专有云的 Java EE 应用,因此这里不考虑安卓 apk 什么事了。复杂的 JavaEE 应用一般是多 module 的,可能涉及不同 module 的 jar 包依赖、各种写着类名的配置文件,但用到反射的情况并不多,主要是某些 AOP、hack 之类的。因此需要小心的混淆,了解混淆的每一个配置及可能带来的副作用。这里我们仅仅对代码进行适度的混淆,示例中并没有考虑应用中的反射,但一般场景下已经足够。
假设应用名称是
,应用名称与 IDE 里项目名称相同,项目下有一些子模块(Module),名叫 module-1、module-2……,应用代码都属于
- $APP_NAME
包下。我们首先创建配置文件在 $APP_NAMEtoolsproguardproguard.conf(单独抽到配置文件里,比写到 pom.xml 里更易读),目录结构大致如下:
- com.company.appname
- $APP_NAME
- ├module-1
- │ └pom.xml
- ├module-2
- │ └pom.xml
- ├tools
- │ └proguard
- │ └proguard.conf
- └pom.xml
配置文件
内容如下:
- proguard.conf
- # 忽略警告
- -ignorewarnings
- #打印处理信息,失败时会打印堆栈信息
- -verbose
- # 保持目录结构
- -keepdirectories
- #不能混淆泛型、抛出的异常、注解默认值、原始行号等
- -keepattributes Signature,Exceptions,*Annotation*,InnerClasses,Deprecated,EnclosingMethod
- # 对于包名、类名不进行混淆
- -keeppackagenames com.company.appname.**
- # 保留public、protected方法不被混淆
- -keep public class * {
- public protected *;
- }
- # 保留注解不被混淆
- -keep public @interface * {
- ** default (*);
- }
- # 保留枚举类不被混淆
- -keepclassmembers enum * {
- public static **[] values();
- public static ** valueOf(java.lang.String);
- }
- # 保持依赖注入不被混淆
- -keepclassmembers class * {
- @org.springframework.beans.factory.annotation.Autowired *;
- @javax.annotation.Resource *;
- }
- # 保持RMI调用不被混淆
- -keep class * implements java.rmi.Remote {
- <init>(java.rmi.activation.ActivationID, java.rmi.MarshalledObject);
- }
- # 保留JavaBean不被混淆
- -keepclassmembers class * implements java.io.Serializable {
- static final long serialVersionUID;
- private static final java.io.ObjectStreamField[] serialPersistentFields;
- private void writeObject(java.io.ObjectOutputStream);
- private void readObject(java.io.ObjectInputStream);
- java.lang.Object writeReplace();
- java.lang.Object readResolve();
- }
- # 避免类名被标记为final
- -optimizations !class/marking/final
然后在
中加入对
- $APP_NAME/pom.xml
的定义,避免每个 module 里都把公共的代码写一遍:
- proguard-maven-plugin
- <?xml version="1.0" encoding="UTF-8"?>
- <project ...>
- ....
- <build>
- ....
- <pluginManagement>
- <plugins>
- ....
- <plugin>
- <groupId>com.github.wvengen</groupId>
- <artifactId>proguard-maven-plugin</artifactId>
- <version>2.0.14</version>
- <dependencies>
- <dependency>
- <groupId>net.sf.proguard</groupId>
- <artifactId>proguard-base</artifactId>
- <version>5.3.3</version>
- <scope>runtime</scope>
- </dependency>
- </dependencies>
- <executions>
- <execution>
- <phase>package</phase>
- <goals>
- <goal>proguard</goal>
- </goals>
- </execution>
- </executions>
- <configuration>
- <obfuscate>true</obfuscate>
- <proguardInclude>../tools/proguard/proguard.conf</proguardInclude>
- </configuration>
- </plugin>
- ....
- </plugins>
- </pluginManagement>
- ....
- </build>
- ....
- </project>
同时在每一个 module 的
文件里,加入对
- pom.xml
的引用:
- proguard-maven-plugin
- <?xml version="1.0" encoding="UTF-8"?>
- <project ...>
- ....
- <build>
- ....
- <plugins>
- ....
- <plugin>
- <groupId>com.github.wvengen</groupId>
- <artifactId>proguard-maven-plugin</artifactId>
- </plugin>
- ....
- </plugins>
- ....
- </build>
- ....
- </project>
配置文件、pom.xml 文件配完,后续开发、打包、上发布系统就和普通的应用没有任何区别了,maven 打包完的
所在目录下有一个同名的
- $filename.jar
.jar.original 包是未经混淆的包。
- $filename
根据前一节中的配置进行混淆,可以看到源文件行号已经无法还原,普通成员变量、本地变量的变量名已经替换成无意义名字,代码结构有很细微的变化不影响结果。经过混淆和优化后,比原始的 class 文件小了大致 23%。
对于不被其他应用代码依赖的应用和需要发布为二方包被别的应用依赖的应用,配置可能不同。二方包里的类名、方法名不可混淆,同时可以通过混淆阻止其他应用通过反射来进行不安全的调用,当然对公共数据结构里的方法不可混淆。对于直接发布到服务器上最终使用的应用,类名、变量名,甚至配置文件都可以进行混淆,对于需要被反射的一些类,方法名甚至类名不能被混淆,如装配时 By name 和 By Type 就有很大区别。
比如 JavaBean 混淆后,类成员变量的名称可以变掉,方法名不变。这时候如果成员变量有注解类似于
、
- @JsonIgnore
可能会失效,正确的应该把这些注解写到 Setter 方法上。
- @JSONField(serialize=XX)
混淆可以优化代码,去除字节码中关联的行号信息,这时候如果出错,日志会相对难调试。这个是双刃剑,要么接受混淆,要么通过控制参数保留行号信息。
《 Protect Your Java Code — Through Obfuscators And Beyond 》
《 Tips for using ProGuard with Spring framework 》
《 ProGuard Examples 》
《 ProGuard Usage 》
《 proguard-maven-plugin 》
来源: http://click.aliyun.com/m/37159/