使用Proguard混淆代码打造APP安全第一层防护
作者: | 蒋东国 |
时间: | 2017年02月12日 星期日 |
应用来源: | hqt APP(测试终端:华为Nova,三星C9 Pro) |
博客地址: | http://blog.csdn.net/andrexpert/article/details/55002749 |
ProGuard是一个可用于对Java字节码文件进行压缩、优化、混淆以及预校验的免费工具,它能够移除工程中一些没有用的代码,或者使用语义上隐晦的名称来重命名代码中的类、字段和函数等,达到压缩、优化和混淆代码的功能。它的具体功能如下:
a. 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性;
b. 优化(Optimize):分析和优化相关方法字节码,移除无用的指令;
c. 混淆(Obfuscate):使用随机的小写且无意义的名称对类、字段和方法进行重命名;
d. 预校验(Preverify):在Java平台上对处理后的代码进行预校验,确保加载的class文件是可执行的。
2. ProGuard工作原理
ProGuard的工作原理包括四个部分,即压缩、优化、混淆、预校验,它们能够使最终生成的APK文件变得更小、效率更高、更难被反编译。ProGuard工作流程如下:
Java是一种跨平台的解释性语言,它的源代码编译成中间”字节码”存储于class文件中。由于跨平台的需要,Java字节码中包括了很多源代码信息,如变量名、方法名,它们被用来访问相关的变量和方法,而这些符号恰恰带有很多语义信息,极易被反编译成Java源代码从而造成开发的应用被轻松破解。为了防止这种情况,我们可以使用ProGuard实现对Java字节码进行混淆。所谓混淆,是指ProGuard将发布出去的程序进行重新组织和处理,它能够将代码中的所有变量、方法、类名转换为极为简短且无任何意义的英文字母,在缺乏相应函数名和程序注释情况下,即使被反编译也将难以阅读,并且在混淆的过程中一些不影响正常运行的信息将永久丢失造成程序更加难以理解,最终达到保护应用源代码的效果。
2. ProGuard代码混淆原则
(1) 代码中使用了反射,则不能混淆。比如一些ORM框架的使用,需要保证类名、方法不便,要不然混淆后就无法实现反射;
-keepattributes Signature
-keepattributes EnclosingMethod
(2) 使用GSON、Fastjson等JSON解析框架所生成的对象类生成的bean实体对象不能混淆,因为其内部大多数是通过反射来生成的;
(3) 引用了第三方开源框架或继承第三方SDK,如开源的okhttp网络访问框架,百度定位SDK等在这些第三库的文档中通常会给出相应的混淆规则。如果无法确定相关的混淆规则,采用如下方法也可,以fastjson框架为例:
-libraryjars libs/fastjson-1.1.36.jar
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.fastjson.** { *; }
(4) 继承了Serializable接口的类不能混淆,因为在反序列化时需要正确的类名等;
(5) AndroidMainfest中的类及其子类不能混淆,包括四大组件、Application的子类等;
(6) Parcelable的子类和Creator静态成员变量不混淆,否则会产生android.os.BadParcelableException异常;
-keep class * implements android.os.Parcelable{
publicstatic final android.os.Parcelable$Creator *;
}
(7) 使用WebView的JS调用接口不能混淆;
-keepclassmembersclass fqcn.of.javascript.interface.for.webview {
public *;
}
-keep classcom.xxx.xxx.** { *; }
(8) 所有的JNI方法不能混淆
-keepclasseswithmembernamesclass * {
native <methods>;
}
(8) 不混淆Enum类型的指定方法
-keepclassmembers enum * {
publicstatic **[] values();
publicstatic ** valueOf(java.lang.String);
}
3. ProGuard参数详解
参数 | 作用 |
-optimizationpasses | 指定混淆压缩比,0~7之间,默认-optimizationpasses 5 |
-dontusemixedcaseclassnames | 混淆时不使用大小写混合,混淆后的类名为小写 |
-dontskipnonpubliclibraryclasses | 指定不去忽略非公共的库类 |
-dontskipnonpubliclibraryclassmembers | 指定不去忽略包可见的库类的成员 |
-dontpreverify | 不做预校验,安卓不需要preverify,去掉这一步可加快混淆速度 |
-verbose | 混淆后就会生成映射文件,包含有类名->混淆后类名的映射关系等 |
-dontoptimize | 优化,不优化输入的类文件 |
-allowaccessmodification | 优化时允许访问并修改有修饰符的类和类的成员 |
-printmapping | 指定映射文件的名称,如-printmapping proguardMapping.txt |
-dontshrink | 压缩,不压缩输入的类文件 |
-optimizations | 指定混淆时采用的算法,后面的参数是一个过滤器 -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* |
-keepattributes [attribute_filter] | 不混淆相关属性,比如: (1) -keepattributes *Annotation* 不混淆代码中的注释(在JSON实体映射中非常重要); (2) -keepattributes Signature,不混淆泛型; (3) -keepattributes SourceFile,LineNumberTable 抛出异常保留代码行号 |
-keep [,modifier,...] class_specification | 不混淆指定的类文件和类的成员(变量、方法),比如: (1) -keep public class * extends android.app.Activity 不混淆所有Activity的子类 (2) -keep public class com.xxxx.app.ui.fragment.** {*;} 不混淆android-support-v4.jar包下的所有类及类的成员 |
-keepclassmembers [,modifier,...] class_specification | 不混淆指定类的成员,比如: (1) -keepclassmembers class * extends android.app.Activity { public void *(android.view.View);} 不混淆所有Activity的子类中参数是view的方法; (2) -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String);} 不混淆所有枚举类 |
-keepclasseswithmembers [,modifier,...] class_specification | 不混淆指定的类和类的成员,但条件是所有指定的类和类成员必须存在,如保留所有的本地native方法不被混淆: -keepclasseswithmembernames class * { native <methods>; } |
-keepnames class_specification | 不混淆指定的类和类的成员的名称 |
-keepclassmembernames | 不混淆类的成员名 |
-keepclasseswithmembernames | 不混淆类的及其成员名 |
-printseeds {filename} | 列出类和类的成员-keep选项的清单,标准输出到给定的文件 |
-libraryjars class_path | 声明引入的第三方库,比如支付宝SDK: -libraryjars libs/alipaysdk.jar |
-dontwarn | 不提示引入第三方库发出的警告 -dontwarn com.alipay.android.app.** |
文件过滤(File Filters) | (1) ?(问号):匹配文件名中的一个字符 (2) *(单星号):匹配任何一个文件名,但不包含目录分隔符 (3) **(双信号):匹配任何一个文件名,及任意数量的目录分隔符 (4)!(感叹号):匹配除!之外的内容 举例: java/*.class,匹配java目录下的所有java文件,不包子目录 java/**.class,匹配所有Java文件,包括子目录下的java文件 !**.gif,images/**,匹配images目录下所有除gif格式文件 |
详见:android-sdk-windows/tools/proguard/docs/manual/usage.html
4. ProGuard项目实例
(1) project.properties
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-20
android.library=false
dex.force.jumbo=true
#指明引用的本地库
android.library.reference.1=..\\HWPUSH
(2) proguard-project.txt
-dontoptimize
-dontpreverify #忽略与dex编译器和Dalvik VM不相干的预校验
-verbose #开启混淆记录日志,指定映射文件的名称
-printmapping proguardMapping.txt
-keepattributes *Annotation* #保留注释,RemoteViews通常需要annotations
-dontusemixedcaseclassnames # 混淆时不使用大小写混合,混淆后的类名为小写
-dontskipnonpubliclibraryclasses # 指定不去忽略非公共的库的类
-dontskipnonpubliclibraryclassmembers # 指定不去忽略非公共的库的类的成员
###########################################################
#### 基本规则 #####
###########################################################
#四大组件(AndroidManifest.xml清单文件中内容)等不混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
#自定义控件不混淆
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
}
#包含特殊参数的构造方法及其类不混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#所有的Onclick操作不混淆
-keepclassmembers class * extends android.content.Context {
public void *(android.view.View);
public void *(android.view.MenuItem);
}
# 对于R(资源)类中的静态方法不能被混淆
-keepclassmembers class **.R$* {
public static <fields>;
}
#Javascript接口中的方法不能混淆
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
###########################################################
#### 根据项目情况自定义规则 #####
###########################################################
#不混淆注释
-keepattributes *Annotation*
#所有Native方法以及它们所在的类名不混淆
-keepclasseswithmembernames class * {
native <methods>;
}
#枚举类中所有的静态方法不混淆
-keepclassmembers,allowoptimization enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#所有Parcelable子类静态方法不混淆
-keepclassmembers class * implements android.os.Parcelable {
static android.os.Parcelable$Creator CREATOR;
}
# 保留Serializable序列化的类不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
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();
}
#反射不混淆
-keepattributes Signature
-keepattributes EnclosingMethod
#不混淆所有Bean类,根据异常指定某些类不混淆
-keep class com.luchibao.example.bean.** {*;}
-keep class com.luchibao.example.dao.** {*;}
# 申明所有jar包、so动态库
-libraryjars libs/fastjson-1.1.36.jar
-libraryjars libs/greendao-1.3.7.jar
-libraryjars libs/litehttp.jar
-libraryjars libs/armeabi/libpocketsphinx_jni.so
-dontwarn edu.cmu. pocketsphinx.**
-keep class edu.cmu. pocketsphinx.** { *; }
#不混淆fastjson-1.1.36.jar
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.fastjson.** { *; }
#不混淆greenrobot.jar
-dontwarn de.greenrobot.dao.**
-keep class de.greenrobot.dao.** { *; }
#不混淆litehttp.jar
-dontwarn com.litesuits.**
-dontwarn com.google.gson.**
-keep class com.google.gson.** { *; }
-keep class com.litesuits.http.** { *; }
-keep class com.litesuits.android.** { *; }
注意:如果将jar包放在lib目录下引用,Proguard无法对其进行忽略,从而被混淆。但由于jar包通常已经被混淆过,如果再次混淆将会出现很多错误。对于这种情况,建议将lib目录下的jar包移动到libs目录下。
(3) 效果演示
通过jeb.exe或者dex2jar、jd-gui.exe查看混淆后的apk或classes.class文件:
码字不易,转载请声明出处:http://blog.csdn.net/andrexpert/article/details/55002749
最后的话:开发当中遇到相关问题
(1) java.io.IOException: Can't read[project_path\libs\aMcuSdk_7.0.14.108.jar] (Can't process class [d.class](Unknown verification type [113] in stack map frame))
解决:Proguard工具本身问题,修改Proguard源码中ClassConstants.java文件
publicstatic final String ATTR_StackMapTable = "StackMapTable";
改为 public static final String ATTR_StackMapTable = "dummy";
参考:http://blog.csdn.net/jiguangcanhen/article/details/41806599
http://blog.csdn.net/zhongzhenyiyu/article/details/52994635
(2) java.io.IOException: The same input jar[project_path\libs\baidumapapi_map_v3_7_3.jar] is specified twice.
解决:最新版的Proguard(5.3)默认对libs目录下所有第三方库进行引用,如果再在 proguard-project.txt进行-libraryjars引用,将会报此错误。另外,AudioStudio也不需要再次对第三方库进行-libraryjars引用。
(3) ClassNotFoundException异常
解决: 两种情况
一是诸如反射相关类被混淆导致错误,需要通过keep对其进行过滤掉;
二是存在第三方库没有过滤问题导致被混淆
其他异常,详见android-sdk-windows/tools/proguard/docs/manual/usage.html(Troubleshooting)