任何一个商业化的App发布都需要经历代码混淆和打包这两个步骤,混淆是因为当前有些工具比如apktool,dextojar等是可以对android安装包进行反编译,获得源码的。为了减少被别人破解,导致源码泄露等问题,我们需要对代码进行混淆,然后通过打包发布App项目。
不过在讨论如何对代码混淆打包之前,我们先了解一下如何进行反编译,这样才会感觉到代码混淆的必要性。
这里我们试着用apktool工具来反编译一个工程,首先我们通过as来创建一个简单的项目MyTest,里面只有一个MainActivity的java文件,我们对文件不做混淆直接打包成apk(打包后面有讲解),然后我们将配置好的apktool装入path环境中去,在终端中输入apktool,如果出现了很多提示说明表示可以使用运行。然后我们在终端中输入
apktool d /Users/hkxlegend/Git/MyTest.apk
然后我们打开反编译后的文件夹目录(和apk同一个名字)如下,点击AndroidManifest和res文件,发现里面的都是可以看见的。
我们发现在这个文件夹中有一个smali文件夹,我们点开文件夹后发现有很多 .smali 文件,我们可以把它理解成为可以运行在java虚拟机上的汇编语言
可以看到这里的文件和我们app中项目结构很像,还有我们唯一的MainActivity也在这里,现在我们查看一下这个smali文件,我们可以通过 Sublime Text 工具栏中File—> open方式打开这个工程
我们可以清晰的看到项目结构中的每一个类和里面的对应的smali代码,通过smali语法就能轻易的知道代码做了什么,然后可以改动代码通过apktool输入 “apktool b” 命令重新编译代码,这种行为对于项目的安全性和隐私性是极不利的,所以,为了保证项目具有更好的隐私性,我们需要对项目代码进行混淆。
代码混淆是一种增加破解难度的手段,并不意味着一定能完全保护程序的隐私性。 我们在使用Android Studio创建的项目后,会在项目侧根目录下生成一个proguard-rules.pro文件,此文件便是混淆规则文件,那么proguard-rules.pro原理是什么呢
Java代码编译成二进制class 文件,这个class 文件也可以反编译成源代码 ,除了注释外,原来的code 基本都可以看到。为了防止重要code 被泄露,我们往往需要混淆(Obfuscation code , 也就是把方法,字段,包和类这些java 元素的名称改成无意义的名称,这样代码结构没有变化,还可以运行,但是想弄懂代码的架构却很难。 proguard 就是这样的混淆工具,它可以分析一组class 的结构,根据用户的配置,然后把这些class 文件的可以混淆的java 元素名混淆掉。在分析class 的同时,他还有其他两个功能,删除无效代码(Shrinking 收缩),和代码进行优化 (Optimization Options)
知道了原理后我们在gradle配置中加入,开始混淆
buildTypes {
release {
//获取SDK下'proguard-android.txt‘文件中的默认混淆规则
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
minifyEnabled true //设置代码混淆
}
}
这样配置后当我们通过release方式打包时候就会进行代码混淆。针对于代码混淆我们还要知道,有些代码是不可以进行混淆的,需要预设保留选项(配置不进行处理的内容)
- 我们用到反射的地方
- WebView中JavaScript调用的方法
- android Manifest文件中的activity,service,provider, receviter,等都不能进行混淆。一些在xml中配置的view也不能进行混淆,android提供的默认配置中都有。
- Android Parcelable ,需要使用android 序列化的
- Java序列化方法,系统序列化需要固定的方法
- 枚举 ,系统需要处理枚举的固定方法
- 本地方法,不能修改本地方法名
- Jni接口,系统接口
- 第三方库
然后我们看一下proguard-rules.pro文件中都可以包含有一些什么样的配置(罗列一些常用的)
- 不混淆某个类
-keep public class com.rensanning.example.Test
- 不混淆某个包所有的类
-keep class com.rensanning.example.*
- 不混淆某个类的子类
-keep public class * extends com.rensanning.example.Test
- 不混淆某个接口的实现
-keep class * implementscom.rensanning.example.TestInterface {
public static final com.rensanning.example.TestInterface$Creator *;
}
- 不混淆某个类的特定的方法
-keepclassmembers class
com.rensanning.example.Test {
public void setTestString(java.lang.String);
}
- 不提示警告
-dontwarn android.support.v4.*{;}
然后让我们看一个项目中的实例代码
-ignorewarnings # 忽略警告,避免打包时某些警告出现 -optimizationpasses 5 # 指定代码的压缩级别 -dontusemixedcaseclassnames # 是否使用大小写混合 -dontskipnonpubliclibraryclasses # 是否混淆第三方jar -dontpreverify # 混淆时是否做预校验 -verbose # 混淆时是否记录日志 -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* # 混淆时所采用的算法
-libraryjars libs/treecore.jar
-dontwarn android.support.v4.** #缺省proguard 会检查每一个引用是否正确,但是第三方库里面往往有些不会用到的类,没有正确引用。如果不配置的话,系统就会报错。 -dontwarn android.os.** -keep class android.support.v4.** { *; } # 保持哪些类不被混淆 -keep class com.baidu.** { *; } -keep class vi.com.gdi.bgl.android.**{*;} -keep class android.os.**{*;}
-keep interface android.support.v4.app.** { *; } -keep public class * extends android.support.v4.** -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.support.v4.widget -keep public class * extends com.sqlcrypt.database -keep public class * extends com.sqlcrypt.database.sqlite -keep public class * extends com.treecore.** -keep public class * extends de.greenrobot.dao.**
-keepclasseswithmembernames class * { # 保持 native 方法不被混淆 native <methods>;
}
-keepclasseswithmembers class * { # 保持自定义控件类不被混淆 public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * { # 保持自定义控件类不被混淆 public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity { //保持类成员 public void *(android.view.View);
}
-keepclassmembers enum * { # 保持枚举 enum 类不被混淆 public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable { # 保持 Parcelable 不被混淆 public static final android.os.Parcelable$Creator *;
}
-keep class MyClass; # 保持自己定义的类不被混淆
然后我们反编译一下这个混淆代码后的项目,可以看到很多代码已经看不见了
这样就达到了代码混淆的目的
打包发布是作为app开发的最后一步,这时应用程序应该已经开发完成,并且去掉了内部的调试信息,然后我们需要给程序进行签名。
数字签名作用也是很好理解的,首先,要确定消息的来源确实是其申明的那个人;其次,要保证信息在传递的过程中不被第三方篡改,即使被篡改了,也可以发觉出来,这就是数字签名的作用。数字签名其实就是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。
debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
同样签名可以分为debug签名和release签名,debug版本的签名信息和release版本的签名信息不同,debug打包会用到默认的debug.keystore签名文件( $HOME/.android/debug.keystore),不同签名的apk是不能安装到同一个手机上的,如果你的手机上装了一个debug版本的apk,如果不卸载就直接安装release版本的apk就会出错,这个在软件更新的环节上也非常重要,具体区别为
1)debug签名的应用程序不能在Android Market上架销售,它会强制你使用自己的签名(release签名形式),Debug模式下签名用的证书自从它创建之日起,1年后就会失效。
2)debug.keystore在不同的机器上所生成的可能都不一样,就意味着如果你换了机器进行apk版本升级,那么将会出现程序不能覆盖安装的问题,相当于软件不具备升级功能
我们上面提到的签名就是所有的应用程序都必须有数字证书,Android系统不会安装一个没有数字证书的应用程序,Android程序包使用的数字证书可以是自签名的,不需要一个权威的数字证书机构签名认证。并且数字证书都是有有效期的,Android只是在应用程序安装的时候才会检查证书的有效期,如果程序已经安装在系统中,即使证书过期也不会影响程序的正常功能。
1. 修改配置文件
在 Android Studio 中打开 build.gradle(Module中)的 android 节点下加入如下配置
//独立配置签名信息
signingConfigs {
release {
storeFile file("myOwn.Keystore")
storePassword sign_storePwd
keyAlias "myOwn"
keyPassword sign_keyPwd
}
}
//构建方式
buildTypes {
release {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
debuggable false //关闭debug模式
jniDebuggable false //Native代码的调试
signingConfig signingConfigs.release //使用release签名
minifyEnabled true //设置代码混淆
}
debug {
applicationIdSuffix ".debug" //包名加debug后缀
zipAlignEnabled true //zip对齐
}
}
可以看到我们上面在signingConfigs中采用独立配置的方式,对于签名相关的信息,直接写在gradle当然不好,特别是一些开源项目,可以添加到gradle.properties中
sign_storePwd 12345678
sign_keyPwd 12345678
buildTypes中有两种构建方式,release和debug方式,debug采用默认的keystore我们没有进行配置,一些配置含义已经加上了标注信息。
2. 创建keystore文件打包
1)Android Studio菜单Build->Generate Signed APK
2)接下来,我们创建密钥库及密钥,创建后会自动选择刚创建的密钥库和密钥,如果没有签名文件我们需要点击“ Create new… ”按钮创建密钥库
Key store path:密钥库文件的地址
Password/Confirm:密钥库的密码
Key:
Alias:密钥别名
Password/Confirm:密钥密码
Validity(years):密钥有效时间
First and Last Name:密钥颁发者姓名
Organizational Unit:密钥颁发组织
City or Locality:城市
Country Code(XX):国家
3)ok,现在我们就可以用生成的这个签名文件对项目进行打包了,点击Next后会发现有两种打包方式release和debug,我们选择一种方式点击Finish打包就完成了
然后在我的项目工程中就可以看到生成的apk文件,我用两种方式都打包了,所以就生成了两个apk文件
这样就成功打包生成了我们自己的apk。至此为止,我们已经对混淆打包流程有了一个大体的了解,关于这个流程就先介绍到这里吧。