Java代码混淆工具ProGuard

一、简介

Java代码的混淆不只有ProGuard,还有像yGuard、Facebook ProGuard分支等,但ProGuard在被使用上占有绝对优势,常见问题更容易找到解决办法。

ProGuard是一个混淆代码的开源项目,它的主要作用是混淆代码,ProGuard包括以下4个功能。

压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)。

优化(Optimize):对字节码进行优化,移除无用的指令。

混淆(Obfuscate):使用a,b,c,d这样简短而无意义的名称,对类、字段和方法进行重命名。

预检(Preveirfy):在Java平台上对处理后的代码进行预检,确保加载的class文件是可执行的。

根据官网的翻译:Proguard是一个Java类文件压缩器、优化器、混淆器、预校验器。压缩环节会检测以及移除没有用到的类、字段、方法以及属性。优化环节会分析以及优化方法的字节码。混淆环节会用无意义的短变量去重命名类、变量、方法。这些步骤让代码更精简,更高效,也更难被逆向(破解)。

 

这里只讲部分ProGuard混淆的在开发工具(以Eclipse为例)中简单使用。

ProGuardJava开发的也有GUI版本等其他形式,有兴趣的自行研究使用。

 

 

二、参数

1. 基本指令

# 代码混淆压缩比,在07之间,默认为5,一般不需要改

-optimizationpasses 5

 

# 混淆时不使用大小写混合,混淆后的类名为小写

-dontusemixedcaseclassnames

 

# 指定不去忽略非公共的库的类

-dontskipnonpubliclibraryclasses

 

# 指定不去忽略非公共的库的类的成员

-dontskipnonpubliclibraryclassmembers

 

# 不做预校验,preverifyproguard4个步骤之一

# Android不需要preverify,去掉这一步可加快混淆速度

-dontpreverify

 

# 有了verbose这句话,混淆后就会生成映射文件

# 包含有类名->混淆后类名的映射关系

# 然后使用printmapping指定映射文件的名称

-verbose

-printmapping proguardMapping.txt

 

# 指定混淆时采用的算法,后面的参数是一个过滤器

# 这个过滤器是谷歌推荐的算法,一般不改变

-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

 

# 保护代码中的Annotation不被混淆,这在JSON实体映射时非常重要,比如fastJson

-keepattributes *Annotation*

 

# 避免混淆泛型,这在JSON实体映射时非常重要,比如fastJson

-keepattributes Signature

 

//抛出异常时保留代码行号,在异常分析中可以方便定位

-keepattributes SourceFile,LineNumberTable

 

-dontskipnonpubliclibraryclasses用于告诉ProGuard,不要跳过对非公开类的处理。默认情况下是跳过的,因为程序中不会引用它们,有些情况下人们编写的代码与类库中的类在同一个包下,并且对包中内容加以引用,此时需要加入此条声明。

 

-dontusemixedcaseclassnames,这个是给Microsoft Windows用户的,因为ProGuard假定使用的操作系统是能区分两个只是大小写不同的文件名,但是Microsoft Windows不是这样的操作系统,所以必须为ProGuard指定-dontusemixedcaseclassnames选项

 

例:(此示例为pom.xml文件配置形式,下同)



参考:https://www.guardsquare.com/en/products/proguard/manual/usage

 

2.不混淆的内容

注意:所有预加载、项目启动初始化的类,被jar包引用的类(本身也不建议这么写),继承自项目所使用框架或其他依赖的类的子类,枚举类,自定义控件、过滤器,Serializable序列化的类等,以上这些不建议进行混淆。

后期谁如有遇到其他不能进行混淆的,请自行补遗!

 

#需要保持的属性:异常,注解等

-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod

 

#不混淆所有的set/get方法

-keepclassmembers public class * {void set*(***);*** get*();}

 

# 保留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 *;}

 

# 避免类名被标记为final

-optimizations !class/marking/final

 

# 保留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(); }

JavaBean不被混淆也可直接写包路径,如下:

-keep class com.xxx.xxx.bean.** { *;}

 

#不混淆包下的所有类名,且类中的方法也不混淆的写法

-keep class com.xxx.xxx.xxx { <methods>; }

-keep class com.xxx.xxx.xxx.** { <methods>; }

-keep class com.xxx.xxx.xxx.** { *; }

 

例:




三、配置

这里使用pom.xml的配置形式,在pom.xml中加入

 

<!-- ProGuard混淆插件-->

<plugin>

   <groupId>com.github.wvengen</groupId>

   <artifactId>proguard-maven-plugin</artifactId>

   <version>2.0.14</version>

   <executions>

      <execution>

         <!-- 混淆时刻,这里是打包的时候混淆-->

         <phase>package</phase>

         <goals>

            <!-- 使用插件的什么功能,混淆-->

            <goal>proguard</goal>

         </goals>

      </execution>

   </executions>

   <configuration>

      <!-- 是否将生成的PG文件安装部署-->

      <attach>true</attach>

      <!-- 是否混淆-->

      <obfuscate>true</obfuscate>

      <!-- 指定生成文件分类 -->

      <attachArtifactClassifier>pg</attachArtifactClassifier>

      <options>

         <!-- JDK目标版本1.8-->

         <option>-target 1.8</option>

         <!-- 不做收缩(删除注释、未被引用代码)-->

         <option>-dontshrink</option>

         <!-- 不做优化(变更代码实现逻辑)-->

         <option>-dontoptimize</option>

         <!-- 不路过非公用类文件及成员-->

         <option>-dontskipnonpubliclibraryclasses</option>

         <option>-dontskipnonpubliclibraryclassmembers</option>

         <!--不用大小写混合类名机制-->

         <option>-dontusemixedcaseclassnames</option>

         <!-- 优化时允许访问并修改有修饰符的类和类的成员 -->

         <option>-allowaccessmodification</option>

         <!-- 确定统一的混淆类的成员名称来增加混淆-->

         <option>-useuniqueclassmembernames</option>

         <!-- 不混淆所有包名-->

         <!--<option>-keeppackagenames</option>-->

         <!-- 需要保持的属性:异常,注解等-->

         <option>-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod</option>

         <!-- 不混淆所有的set/get方法->

         <option>-keepclassmembers public class * {void set*(***);*** get*();}</option>

         <!-- 不混淆包下的所有类名,且类中的方法也不混淆-->

         <option>-keep class com.xxx.xxx.bboss.SystemConfig { <methods>; }</option>

         <option>-keep class com.xxx.xxx.xxx.controller.** { <methods>; }</option>

         <option>-keep class com.xxx.xxx.xxx.exception { <methods>; }</option>

         <option>-keep class com.xxx.xxx.xxx.model.** { *; }</option>

      </options>

      <!--class 混淆后输出的jar包-->

      <outjar>classes-autotest.jar</outjar>

      <!-- 添加依赖,这里你可以按你的需要修改,这里测试只需要一个JRE的Runtime包就行了 -->

      <libs>

         <lib>${java.home}/lib/rt.jar</lib>

      </libs>

      <!-- 对什么东西进行加载,这里仅有classes成功-->

      <injar>classes</injar>

      <!-- 输出目录-->

      <outputDirectory>${project.build.directory}</outputDirectory>

   </configuration>

</plugin>


  以上参数和相关配置根据自身项目及需求进行相应修改

后续就是正常的打包流程,更新项目,运行 mvn clean package -DskipTests

如下:



data-check-pg.jar中是混淆之后的class,未包含任何配置等内容,只有\classes路径内容;

data-check.war即为混淆后的web项目(若没有混淆,则把data-check-pg.jar解压拷入);

proguard_map.txt混淆内容映射;

proguard_seed.txt参与混淆的类

混淆效果:可以看到源文件行号已经无法还原,普通成员变量、本地变量的变量名已经替换成无意义名字,代码结构有很细微的变化不影响结果。经过混淆和优化后,比原始的class文件小了大致20%左右。



混淆后部分包名跟类名已经被改为了简单字母,不再具有业务含义,而且变量名也进行了修改,增加了阅读代码难度。

运行服务,项目正常运行即混淆成功。

 

注意事项:

1、因为有时候会配置不保持包名或类名,因此一些相关配置文件的内容需要改变,好在ProGuard不是随机生成类名,而是先按照原名称对相同包下类进行排序,混淆后的类名称依次为a.class,b.class,c.class.....

那么问题来了,当包中超过26个类时,默认命名为A.class,B.class,C.class,在某些操作系统下,会不区分class文件名称的大小写,会导致错误(水平所限,未深入探究跟类加载相关);因此

<!--不用大小写混合类名机制-->

<option>-dontusemixedcaseclassnames</option>

配置极为关键,该配置会在超过26个类文件时,命名为aa.class,ab.class,ac.class,而不是原来的大写类名,从而避免错误。

 

2、书写代码时需要考虑混淆后是否影响运行

比如JavaBean混淆后,类成员变量的名称可以变掉,方法名不变。这时候如果成员变量有注解类似于@JsonIgnore、@JSONField(serialize=XX)可能会失效,正确的应该把这些注解写到Setter方法上。

或者直接对JavaBean不进行混淆。

 

3、需要考虑Debug的便利性

混淆可以优化代码,去除字节码中关联的行号信息,这时候发布出去的项目如果出错,日志会相对难调试。这个是双刃剑,要么接受混淆,要么通过控制参数保留行号信息。



内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://bjspace.net/post/67

相关推荐

发表吐槽

你肿么看?

你还可以输入 250 / 250 个字

嘻嘻 大笑 可怜 吃惊 抛媚眼 调皮 鄙视 示爱 哭 开心 偷笑 嘘 奸笑 委屈 抱抱 Dog 大兵 威武

评论信息框

吃奶的力气提交吐槽中...


既然没有吐槽,那就赶紧抢沙发吧!