1. 混淆概述
(1). 概念
混淆维基百科的解释
代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为。
(2). 目的
混淆的目的是为了加大反编译的成本,但是并不能彻底防止反编译,比如Android App反编译后虽然代码很难读懂,但依旧是可以读懂的,只是比较费劲;相比较java语言写的程序,C/C++程序反编译后就更难读懂了。
(3). 优缺点
Proguard混淆让代码更加安全, 但是这也使收集的崩溃变得更加难以读懂, 为了解决这个问题我们用 retrace 工具来将混淆后的 StackTrace 还原成混淆之前的信息. retrace脚本 Android 开发环境默认带着retrace脚本,一般情况下路径为./tools/proguard/bin/retrace.sh mapping映射表 Proguard进行混淆之后,会生成一个映射表,文件名为mapping.txt。
(4). Java 代码混淆工具
Android中使用的是Proguard来进行代码混淆的,proguard Java官网对Proguard的定义 Proguard是一个集文件压缩,优化,混淆和校验等功能的工具 它检测并删除无用的类,变量,方法和属性 它优化字节码并删除无用的指令. 它通过将类名,变量名和方法名重命名为无意义的名称实现混淆效果. 最后它还校验处理后的代码,从而使代码更小、更高效、更难破解读懂。
混淆后默认会在工程目录app/build/outputs/mapping/release(debug)下生成一个mapping.txt文件,这就是混淆规则,通过这个文件把混淆后的代码反推回源本的代码,对解决线上出现的bug很有帮助。
(5). 影响元素 代码混淆影响到的元素有
包名
类名
变量名
方法名
其他元素
2. 混淆规则及使用
开启混淆
项目路径下 app 目录下的 build.gradle 文件, 设置minifyEnabled = true 即可
buildTypes {
release {
minifyEnabled true
signingConfig signingConfigs.release
proguardFiles 'proguard-android-optimize.txt', 'proguard-rules.pro'
}
Proguard keep关键字详解:
Proguard keep 关键字 | 功能描述 |
---|---|
-keep | 保留类和类中的成员,防止被混淆或移除 |
-keepnames | 保留类和类中的成员防止被混淆,但成员如果没有被引用将被删除 |
-keepclassmembers | 只保留类中的成员,防止被混淆和移除 |
-keepclassmembernames | 只保留类中的成员,但如果成员没有被引用将被删除 |
-keepclasseswithmembers | 如果当前类中包含指定的方法,则保留类和类成员,否则将被混淆 |
-keepclasseswithmembernames | 如果当前类中包含指定的方法,则保留类和类成员,如果类成员没有被引用,则会被移除 |
(1) . *号的用法:
-keep com.squareup.okhttp3.*
-keep com.squareup.okhttp3.**
末尾一颗星表示只是保持该包下的类名,而子包下的类名还是会被混淆;
末尾两颗星表示把本包和所含子包下的类名都保持。
通过这两种方式保持类后,类名不会未混淆,但里面的具体方法和变量命名还是会被混淆。
(2) . {*;}的用法
-keep com.squareup.okhttp3.*{*;}
-keep com.squareup.okhttp3.**{*;}
第一行既可以保持该包下的类名,又可以保持类里面的内容不被混淆;
第二行既可以保持该包及子包下的类名,又可以保持类里面的内容不被混淆。
(3) . 保持某个类名不被混淆,但是内部内容会被混淆
-keep class com.android.vending
(4) . 保持某个类的类名及内部的所有内容不会混淆
-keep class com.android.vending.** { *; }
(5). 保持类名不被混淆,并且类中特定内容不混淆
-keep class com.jl.test{
public void myTest();
private static final java.lang.String name;
}
其他常用的一些Proguard 关键字
Proguard 关键字 | 功能描述 |
---|---|
-dontwarn | 忽视警告,是一个和keep可以说是形影不离,尤其是处理引入的library时.引入的library可能存在一些无法找到的引用和其他问题,在build时可能会发出警告,如果我们不进行处理,通常会导致build中止.因此为了保证build继续,我们需要使用dontwarn处理这些我们无法解决的library的警告。比如关闭Twitter sdk的警告,我们可以这样做-dontwarn com.twitter.sdk.** |
-dontpreverify | 不做预校验的操作 |
-optimizationpasses | 代码混淆的压缩比例,值在0-7之间,一般为5 |
-keepattributes SourceFile,LineNumberTable | 抛出异常时保留代码行号 |
-dontusemixedcaseclassnames | 混淆后类名都为小写 |
-dontskipnonpubliclibraryclasses | 指定不去忽略非公共的库的类 |
-dontskipnonpubliclibraryclassmembers | 指定不去忽略非公共的库的类的成员 |
-verbose -printmapping proguardMapping.txt | 生成原类名和混淆后的类名的映射文件 |
-keepattributes Signature | 避免混淆泛型 |
-keepattributes Annotation,InnerClasses | 不混淆Annotation |
-optimizations !code/simplification/cast,!field/,!class/merging/ | 指定混淆是采用的算法 |
3. 常用的Android 混淆原则
- 反射用到的类不混淆, 需要保证类名方法不变
- JNI 方法调用的 java 方法不混淆
- Java 的 native 方法
- js 调用的 java 方法
AndroidMainfest 中的类不混淆,四大组件和 Application 的子类和Framework层下所有的类默认不会进行混淆, 四大组件声明必须在manifest中注册,如果混淆后类名更改,而混淆后的类名没有在manifest注册,是不符合Android组件注册机制的. 外部程序可能使用组件的字符串类名,如果类名混淆,可能导致出现异常
-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.view.View
Parcelable的子类和Creator静态成员变量不混淆,否则会产生android.os.BadParcelableException异常
使用GSON、fastjson等框架时,所写的JSON对象类不混淆,否则无法将JSON解析成对应的对象;生成的bean实体对象,内部大多是通过反射来生成, 不能混淆
使用第三方开源库或者引用其他第三方的SDK包时,需要在混淆文件中加入对应的混淆规则; 这些第三库的文档中 一般会给出相应的混淆规则, 复制过来即可
有用到WEBView的JS调用也需要保证写的接口方法不混淆
注解不能混淆 注解在Android平台中使用的越来越多,常用的有ButterKnife和Otto.很多场景下注解被用作在运行时反射确定一些元素的特征. 为了保证注解正常工作,我们不应该对注解进行混淆.Android工程默认的混淆配置已经包含了下面保留注解的配置
-keep attributes Annotation
枚举 enum 不要混淆
使用 enum 类型时需要注意避免以下两个方法混淆,因为enum类的特殊性,以下两个方法会被反射调用
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
4.混淆模板
#指定代码的压缩级别
-optimizationpasses 5
#包名不混合大小写
-dontusemixedcaseclassnames
#不去忽略非公共的库类
-dontskipnonpubliclibraryclasses
#优化 不优化输入的类文件
-dontoptimize
#预校验
-dontpreverify
#混淆时是否记录日志
-verbose
# 混淆时所采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#不混淆注解
-keepattributes *Annotation*
#保持哪些类不被混淆
-keep public class * extends android.app.Fragment
-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
#如果有引用v4包可以添加下面这行
-keep public class * extends android.support.v4.app.Fragment
#忽略警告
-ignorewarning
#如果引用了v4或者v7包
dontwarn android.support.**
-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*(...);
}
#保持 native 方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
#保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#保持 Serializable 不被混淆
-keepnames class * implements java.io.Serializable
#保持 Serializable 不被混淆并且enum 类也不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
#不混淆资源类
-keepclassmembers class **.R$* {
public static <fields>;
}
#避免混淆泛型 如果混淆报错建议关掉
–keepattributes Signature
#移除log 测试了下没有用还是建议自己定义一个开关控制是否输出日志
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}