一、前言
Java代码时非常容易反编译的,但是代码被反编译有可能泄漏核心技术,所以一个安全性高的程序最起码要做到的一件事就是:对代码混淆。
二、混淆的概念和作用
解释:对程序员来说,Utils类的isEmpty()方法可以更好的理解含义,但是对于计算机来说叫A类的b()方法效果是一样的
作用:1、增加APK反编译之后代码泄漏的困难性
2、缩小生成APK的体积
![](https://i-blog.csdnimg.cn/blog_migrate/18bc79e5cc811db38ac68da429b5d5b8.png)
三、混淆工具
Gradle 插件 3.4.0以下:Android Studio默认都是用
ProGuard来执行代码优化和混淆
Gradle 插件 3.4.0以上:默认使用R8进行混淆和压缩,可使用 android.enableR8=false 关闭R8
ProGuard工作原理简介:
ProGuard能够对Java类中的代码进行压缩(Shrink),优化(Optimize),混淆(Obfuscate),预检(Preveirfy)
1. 压缩(Shrink):在压缩处理这一步中,用于检测和删除没有使用的类,字段,方法和属性
2. 优化(Optimize):在优化处理这一步中,对字节码进行优化,并且移除无用指令
3. 混淆(Obfuscate):在混淆处理这一步中,使用a,b,c等无意义的名称,对类,字段和方法进行重命名
4. 预检(Preveirfy):在预检这一步中,主要是在Java平台上对处理后的代码进行预检
对于ProGuard执行流程图如下图所示
![](https://i-blog.csdnimg.cn/blog_migrate/5b38377afa9205a2d550db4156fc2cf4.jpeg)
四、混淆实践
1、混淆配置
-
Android Studio新建项目minifyEnabled默认值为false,将其置为true即可在release包时开启混淆。
-
这里Proguard工具是SDK中自带的,proguard-android-optimize.txt文件为默认混淆规则文件,proguard-rules.pro文件则是自动为我们生成的自定义规则文件,用于开发根据 需求添加自定义混淆规则。
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
2、一个简单的混淆事例
![](https://i-blog.csdnimg.cn/blog_migrate/02b9732b261550d018cea684fb931cde.png)
代码文件
![](https://i-blog.csdnimg.cn/blog_migrate/f25ee7ef54d4e3769407d548d7776509.png)
混淆后的目录
-
首先可看到那个未被引用的类和MainActivity中未调用的方法并未出现在反编译的后的文件中,这是因为ProGuard在混淆前先会进行压缩,主要是移除无效的类、类成员和方法等
-
其次看到MainActivity、MyService、MyTextView的类名均未被混淆。这是因为MainActivity、MyService均是系统组件,事实上凡事在Manifest中注册的所有类名都不会被混淆。而MyTextView作为自定义View,也是没有被混淆的,
-
…………
-
上述的那几个混淆的规则都是在哪里定义的,其实都是写在默认规则proguard-android-optimize.txt文件中的,这个文件存放于<Android SDK>/tools/proguard/proguard-android.txt路径,可以打开看下
3、ProGuard默认混淆文件
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
#
# This file is no longer maintained and is not used by new (2.2+) versions of the
# Android plugin for Gradle. Instead, the Android plugin for Gradle generates the
# default rules at build time and stores them in the build directory.
-dontusemixedcaseclassnames #表示混淆时不使用大小写混合的类名,混淆后均为小写类名
-dontskipnonpubliclibraryclasses #指定不去忽略非公共库的类
-verbose #表示打印混淆的详细信息,即产生混淆前后的映射文件
# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
#默认optimize和preverify选项是关闭的,因为Android的dex并不像Java虚拟机需要optimize(优化)和previrify(预检)两个步骤
-dontoptimize
-dontpreverify #不做预校验,Android不需要preverify,去掉这一步能够加快混淆速度
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.
-keepattributes *Annotation* #表示对注解中的参数进行保留
-keep public class com.google.vending.licensing.ILicensingService #不混淆这俩类,这俩类基本是接入Google原生的一些服务时使用的
-keep public class com.android.vending.licensing.ILicensingService
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
#不混淆任何包含native方法的类的类名以及native方法名。因为C、C++会调用Java代码
-keepclasseswithmembernames class * {
native <methods>;
}
#不混淆任何一个View中的setXxx()和getXxx()方法,因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了
# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
#表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick="buttonClick"属性,当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了
# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
#//保留混淆枚举中的values()和valueOf()方法
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#Parcelable实现类中的CREATOR字段是绝对不能改变的,因为反序列化时底层是会获取静态CREATOR字段的,然后再调用createFromParcel()恢复数据
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
#不混淆R文件中的所有静态字段,我们都知道R文件是通过字段来记录每个资源的id的,字段名要是被混淆了,id也就找不着了
-keepclassmembers class **.R$* {
public static <fields>;
}
#忽略support包因为版本兼容产生的警告
# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
-dontwarn android.support.**
# Understand the @Keep support annotation.
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
4、自定义ProGuard规则
ProGuard关键字
关键字 描述
keep 保留类和类中的成员,防止被重命名或移除
keepnames 保留类和类中的成员,防止被重命名
keepclassmembers 只保留类中的成员,防止被重命名或移除
keepclassmembernames 只保留类中的成员,防止被重命名
keepclasseswithmembers 保留类和类中的成员,防止被重命名或移除,针对特定成员
keepclasseswithmembernames 保留类和类中的成员,防止被重命名,针对特定成员
保留 | 防止被移除或者被重命名 | 防止被重命名 |
类和类成员 | -keep | -keepnames |
仅类成员 | -keepclassmembers | -keepclassmembernames |
如果拥有某成员,保留类和类成员 | -keepclasseswithmembers | -keepclasseswithmembernames |
ProGuard通配符
通配符 描述
<field> 匹配类中的所有字段
<method> 匹配类中所有的方法
<init> 匹配类中所有的构造函数
* 匹配任意长度字符,不包含包名分隔符(.)
** 匹配任意长度字符,包含包名分隔符(.)
*** 匹配任意参数类型
...
可以看如下两个常用命令,很多人可能会迷惑以下两者的区别:
-keep class com.xiaoming.test.**
-keep class com.xiaoming.test.*
-keep class com.xiaoming.test.* {*;}
一颗星表示只保留该包下的类名,而子包下的类名还是会被混淆;两颗星表示把本包和子包下所有的类名都保持,即包含包名分隔符.;如果要保留类名和方法变量名,则可以使用-keep class com.xiaoming.test.* {*;}
除此之外,可以使用java的基本规则来保护特点的类不被混淆,比如我们可以用extend,implement等这些Java规则。如下例子就避免所有继承Activity的类被混淆。
-keep public class * extends android.app.Activity
如果只想保证一个类中特定内容不被混淆,可以使用通配符<field>、<method>、<init> ,并且可以在前面加上权限修饰符来进一步指定不被混淆的内容。如:表示One类下的所有public 方法都不会被混淆
-keep class com.xiaoming.test.One {
public <methods>;
}
还可以加上参数,如以下用JSONObject作为入参的构造函数不会被混淆
-keep class cn.hadcn.test.One {
public <init>(org.json.JSONObject);
}
如果我们要保留一个类中的内部类不被混淆则需要用$符号,如下例子表示保持MyClass内部类JavaScriptInterface中的所有public内容
-keepclassmembers class com.xy.myapp.MyClass$JavaScriptInterface { public *; }
最后看下下面这两个规则的区别
-keepclasseswithmember class * {
native <methods>;
}
保留所有含有native方法的类的类名和native方法名,而如果某个类中没有含有native方法,那就还是会被混淆;
但是如果改成keep关键字,结果会完全不一样
-keep class * {
native <methods>;
}
用keep关键字后,你会发现代码中所有类的类名都不会被混淆了,因为keep关键字看到class *就认为应该将所有类名进行保留,而不会关心该类中是否含有native方法。
5、注意事项,通常哪些项是不能混淆的
1.在AndroidManifest中配置的类,比如四大组件。这些在Android Studio中都是默认的规则
2.JNI调用的方法,因为要和native方法保持一样
3.反射用到的类
4.WebView中JavaScript调用的方法
5. 使用第三方开源库或者引用其他第三方的SDK包时,如果有特别要求,也需要在混淆文件中加入对应的混淆规则。此项需格外注意
6、ProGuard模板代码
一个项目的混淆规则一般都要配置以下几个方面
#—————------一些基本指令添加---------------
......
#-----------Android开发中需要保留的公共部分---------------
......
#-----------处理反射类---------------
......
#-----------处理js交互---------------
......
#-----------处理实体类---------------
......
#-----------处理第三方依赖库---------
......
附:当使用Android Gradle插件3.4.0或更高版本时,插件不再使用ProGuard执行优化而是R8