为什么要混淆?
对要进行发布的应用进行混淆,是为避免源代码与资源文件被反编译导致的劳动成果被窃取、安全漏洞被利用等隐患。
混淆的原理是什么?
将代码中的类名、字段、方法名变为无意义的名称,用于保护源代码。同时由于移除无用的类、方法,并使用简短名称对类、字段、方法进行重命名缩小了程序的大小。
ProGuard由shrink、optimize、obfuscate和preverify四个步骤组成,每个步骤都是可选的,需要哪些步骤都可以在脚本中配置。
-
压缩(Shrink): 侦测并移除代码中无用的类、字段、方法、和特性(Attribute)。
-
优化(Optimize): 分析和优化字节码。
-
混淆(Obfuscate): 使用a、b、c、d这样简短而无意义的名称,对类、字段和方法进行重命名。
-
预检(Preveirfy): 在java平台上对处理后的代码进行预检。
如何在Android里面开启混淆?
在app目录或对应的module目录下的build.gradle的文件中在buildTypes的release里面去做开启与关闭的操作。
buildTypes {
release {
shrinkResources true //开启资源压缩
minifyEnabled true //开启混淆
zipAlignEnabled true //4k对齐
// 混淆文件的位置,其中'proguard-android.txt'为sdk默认的混淆配置,
//'proguard-rules.pro' 是该模块下的混淆配置
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
proguard-rules.pro 默认内容如下:
# 在此处添加特定于项目的ProGuard规则。
# 您可以使用build.gradle中的proguardFiles设置控制应用的配置文件集。
#
# 有关详细信息,请参阅
# http://developer.android.com/guide/developing/tools/proguard.html
# 如果您的项目使用带有JS的WebView,请取消注释以下内容,并为JavaScript接口指定完全限定的类名
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# 取消对此的注释以保留用于调试堆栈跟踪的行号信息。
#-keepattributes SourceFile,LineNumberTable
# 如果保留行号信息,请取消注释以隐藏原始源文件名。
#-renamesourcefileattribute SourceFile
-keep class me.hgj.jetpackmvvm.**{*;}
-keep class com.google.android.material.** {*;}
-keep class androidx.** {*;}
-keep public class * extends androidx.**
-keep interface androidx.** {*;}
-dontwarn com.google.android.material.**
-dontnote com.google.android.material.**
-dontwarn androidx.**
#下面添加第三方依赖库的混淆规则 如XPopup
#xpopup
-dontwarn com.lxj.xpopup.widget.**
-keep class com.lxj.xpopup.widget.**{*;}
Android 混淆的原则
-
反射用到的类不混淆。
-
JNI方法不混淆。
-
AndroidMainfest中的类不混淆,四大组件和Application的子类和Framework层下所有的类默认不会进行混淆。
-
Parcelable的子类和Creator静态成员变量不混淆,否则会产生android.os.BadParcelableException异常。
-
R文件混淆会导致引用错误。
-
使用GSON、fastjson等框架时,所写的JSON对象类不混淆,否则无法将JSON解析成对应的对象。
-
使用第三方开源库或者引用其他第三方的SDK包时,需要在混淆文件中加入对应的混淆规则。
-
有用到WEBView的JS调用也需要保证写的接口方法不混淆。
混淆语法
ProGuard Manual: Usage | Guardsquare
//详细语法
-include {filename} 从给定的文件中读取配置参数
-basedirectory {directoryname} 指定基础目录为以后相对的档案名称
-injars {class_path} 指定要处理的应用程序jar,war,ear和目录
-outjars {class_path} 指定处理完后要输出的jar,war,ear和目录的名称
-libraryjars {classpath} 指定要处理的应用程序jar,war,ear和目录所需要的程序库文件
-dontskipnonpubliclibraryclasses 指定不去忽略非公共的库类。
-dontskipnonpubliclibraryclassmembers 指定不去忽略包可见的库类的成员。
保留选项
-keep {Modifier} {class_specification} 保护指定的类文件和类的成员
-keepclassmembers {modifier} {class_specification} 保护指定类的成员,如果此类受到保护他们会保护的更好
-keepclasseswithmembers {class_specification} 保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在。
-keepnames {class_specification} 保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除)
-keepclassmembernames {class_specification} 保护指定的类的成员的名称(如果他们不会压缩步骤中删除)
-keepclasseswithmembernames {class_specification} 保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后)
-printseeds {filename} 列出类和类的成员-keep选项的清单,标准输出到给定的文件
压缩
-dontshrink 不压缩输入的类文件
-printusage {filename}
-whyareyoukeeping {class_specification}
优化
-dontoptimize 不优化输入的类文件
-assumenosideeffects {class_specification} 优化时假设指定的方法,没有任何副作用
-allowaccessmodification 优化时允许访问并修改有修饰符的类和类的成员
混淆
-dontobfuscate 不混淆输入的类文件
-printmapping {filename}
-applymapping {filename} 重用映射增加混淆
-obfuscationdictionary {filename} 使用给定文件中的关键字作为要混淆方法的名称
-overloadaggressively 混淆时应用侵入式重载
-useuniqueclassmembernames 确定统一的混淆类的成员名称来增加混淆
-flattenpackagehierarchy {package_name} 重新包装所有重命名的包并放在给定的单一包中
-repackageclass {package_name} 重新包装所有重命名的类文件中放在给定的单一包中
-dontusemixedcaseclassnames 混淆时不会产生形形色色的类名
-keepattributes {attribute_name,...} 保护给定的可选属性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and
InnerClasses.
-renamesourcefileattribute {string} 设置源文件中给定的字符串常量
#将android.util.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(...);
}
关于keep 使用探究可以参考
结束
对于防止应用被反编译以及应用中出现的安全漏洞的封堵,这只是一小步;需要学习的东西还很多。