Android 应用签名是应用打包过程的重要步骤之一,Google 要求全部的应用必须被签名才能够安装到 Android 操做系统中。Android 的签名机制也为开发者识别和更新本身应用提供了方便。本文尝试对 Android 应用签名机制进行简单分析,我的理解有限,不免有纰漏之处,请多多拍砖。html
想要搞清楚安卓应用签名究竟是什么东西,首先须要了解一些背景知识。java
数字摘要主要做用是将任意长度的消息使用单向 HASH 算法摘要成一串固定长度的密文。经常使用的 HASH 算法包括 SHA-1, SHA-256, MD5 等等,MD5 中的 MD 就是 Message Digest 的缩写。数字摘要有时也被称为数字指纹,消息摘要等等,其实表达的都是一个意思。它有如下三个特色:android
Android 签名须要用到一种后缀名为 keystore 的文件。在打 Debug 包的时候,若是没有在 build.gradle 文件中指定的话,Gradle 就会自动为咱们生成一个 keystore文件,保存在系统用户根目录 .android 文件夹内,名称为 debug.keystore. 咱们以它为例看看 keystore 文件包含了什么内容。git
经过 keytool 工具来查看,默认的密码是 android. $ keytool -list -v -keystore debug.keystore -storepass android
结果以下:算法
密钥库类型: jks
密钥库提供方: SUN
您的密钥库包含 1 个条目
别名: androiddebugkey
建立日期: 2013-4-26
条目类型: PrivateKeyEntry
证书链长度: 1
证书[1]:
全部者: CN=Android Debug, O=Android, C=US
发布者: CN=Android Debug, O=Android, C=US
序列号: 517a38f2
有效期为 Fri Apr 26 15:46:58 CST 2013 至 Sun Apr 19 15:46:58 CST 2043
证书指纹:
MD5: 37:09:10:A9:F1:AE:9C:E4:C0:85:B9:35:D9:93:93:52
SHA1: F1:60:3F:72:2A:F2:3A:BC:BE:1C:DB:F6:F4:5B:FD:5E:34:8C:01:A9
SHA256: 86:C7:CB:D1:56:E7:D8:B8:AD:67:A7:A1:8F:C0:F6:E6:FC:E1:3D:45:AE:BC:F5:DF:B4:A9:F9:9A:F7:89:F7:0D
签名算法名称: SHA1withRSA
主体公共密钥算法: 1024 位 RSA 密钥
版本: 3
复制代码
能够看到,其实 keystore 相似一个钥匙仓库,里面有证书的全部者和发布者信息,包含了私钥和公钥信息,并设置了密码进行保护。那什么是公钥和私钥呢。shell
公钥和私钥都是公共密钥系统里的概念。最初全部的加密算法都属于对称加密,也就是说加密和解密使用的相同的密码,通讯双方如何安全沟通和保存密码,是这种加密方法的主要难题。难保没有猪队友。安全
而在公共密钥系统中,加密和解密使用的是不一样的密钥,分别称为公钥和私钥,公钥意思就是全部人均可以知道,私钥则只有全部者才持有,单从公钥没法在现有的计算能力下推导出私钥。这样一来就不存在沟经过程中泄露密钥的问题,只要私钥不泄露,通讯就一直是安全的。bash
公共密钥系统能够说是如今最最最重要密钥系统,是互联网的基石之一。oracle
公共密钥系统能够用来加密,也能够用来签名。加密方案中,是不但愿别人知道个人消息,因此公钥用于加密信息,私钥用于解密信息;而签名方案中,是不但愿别人冒充我发消息,只有我才能发布这个签名,因此须要用私钥进行签名,公钥用于验证签名。app
回到正题,咱们先来看看 Android 应用签名发生在构建的哪一步。
在编译过程当中,编译器首先会将源代码和资源进行编译,生成 DEX 文件和一些编译后的资源文件,而后 APK Manager 会根据配置使用 keystore 文件进行签名,签名后才会将全部资源压缩到一个 ZIP 包里,这个 ZIP 包其实咱们安装的时候用的 APK 文件。能够看到签名是在构建基本完成的时候发生的。
那 APK Manager 是如何使用 keystore 进行签名的呢?咱们一步一步看到底发生了什么。
META-INF/MANIFEST.MF
文件中Name: res/drawable-xhdpi-v4/im_ic_keyboard_pressed.png
SHA-256-Digest: cqjOi3gUv9O0IBfgLOlIJUZTBwyCPcWbXIs/o6TMfTc
Name: classes.dex
SHA-256-Digest: FJCwLV1TyZuL1qxkDsJ6bXTmaSkK+JkKt5zmpDBc8Tg=
复制代码
咱们看一下 im_ic_keyboard_pressed.png
这个文件的数字摘要究竟是如何计算出来的。
$ shasum -a 256 im_ic_keyboard_pressed.png
72a8ce8b7814bfd3b42017e02ce948254653070c823dc59b5c8b3fa3a4cc7d37 im_ic_keyboard_pressed.png
复制代码
$ echo "72a8ce8b7814bfd3b42017e02ce948254653070c823dc59b5c8b3fa3a4cc7d37" | xxd -r -p | base64
cqjOi3gUv9O0IBfgLOlIJUZTBwyCPcWbXIs/o6TMfTc=
复制代码
能够看到跟咱们在 MANIFEST.MF 中看到的值是可以对上的。 2. 对 MANIFEST.MF 文件生成数字摘要,并写入 CERT.SF,这里有一个细节,除了对整个文件作 HASH 外,还会将文件分红多段计算 HASH 一样保存在 CERT.SF 文件中【为何,还没搞清楚】 3. 计算 CERT.SF 的数字摘要,并使用 RSA 私钥进行加密,生成签名 4. 将签名、公钥、哈希算法信息写入 CERT.RSA 文文件,并将这些文件添加到 APK 压缩包 META-INF 目录中
目前应用签名不须要申请可信的证书机构 (Certificate Authority) 签发的证书,开发者能够经过 keytool 来建立自签名的证书。
应用签名不能保证 APK 不被篡改,只是为了可以校验出 APK 是否被篡改。在系统安装过程当中,若是发现 APK 被篡改,安装就会失败。那系统是如何校验的呢?
整个校验过程当中,环环相扣,从 CERT.RSA -> CERT.SF -> MANIFEST.SF -> All Other Files,只要有一环失败,系统就会终止 APK 的安装.
若是给新版应用分配了新的签名文件,那就必须更改包名,这样系统才会认为是不一样的应用。不然安装就会失败,提示签名不一致。
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES
除了用于安装时校验应用,签名还有一些别的用途。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:sharedUserId="com.meng.sharedappid" package="meng.mainuicomponents">
复制代码
val friendContext = this.createPackageContext("packageName", Context.CONTEXT_INCLUDE_CODE)
val friendClass = friendContext.classLoader.loadClass("packageName.className")
val noparams = arrayOf<Class<*>>() //say the function (functionName) required no inputs
friendClass.getMethod("functionName", *noparams).invoke(friendClass.newInstance(), null)
复制代码
<!-- 声明权限供兄弟 APP 使用 -->
<permission android:name="com.xxx.permission.SHARED_ACCOUNT" android:protectionLevel="signature" />
<!-- 申请获取兄弟 APP 的声明的权限 -->
<uses-permission android:name="com.xxx.broapp1.permission.SHARED_ACCOUNT" />
<uses-permission android:name="com.xxx.broapp2.permission.SHARED_ACCOUNT" />
<!-- 定义 ContentProvider 来供兄弟 APP 获取共享帐户信息,指定度权限为签名声明的权限 -->
<provider android:name="com.xxx.XXXXSharedAccountProvider" android:authorities="com.xxx.shared_account" android:exported="true" android:readPermission="com.xxx.permission.SHARED_ACCOUNT" />
复制代码
目前签名模式有三代,基本原理都是同样的,只是在流程上有些不一样。
第一代是基于 JAR 文件签名,它主要的缺陷是只保护了一部分文件,而不是对整个 APK 文件作保护。这是由于全部文件都不可能包含了自身的签名,由于它不可能为本身签名后再把签名信息保存到本身内部,这是一个鸡生蛋蛋生鸡的问题,由于这个问题的存在,第一代签名机制会忽略全部以 .SF/.DSA/.RSA 的文件以及 META-INFO 目录下的全部文件。
因此攻击者就能够解压缩后在 APK/META-INF 目录新增一个含有恶意代码的文件,而后再压缩成 APK,一样是能够覆盖安装正版应用的,这样一来好好的应用就会被杀毒软件标记为恶意软件,从而达到攻击应用的目的。 除了容易被攻击外,应用安装起来也比较慢,由于安装器在校验时须要解压计算全部文件的数字摘要,确认没有被恶意修改。
美团打渠道包的方法本质上就利用了这个第一代签名的漏洞,在 META-INF 目录下新建了一个包含 vendor 名称的文件,从而不须要从新编译,只须要解压缩 APK,添加文件,从新压缩就完成了一个渠道包的生成,速度很是快。
Android 7.0 引入了第二代签名,避免了第一代签名模式的问题,主要改进在于它在验证过程当中,将整个 APK 文件看成一个总体,只校验 APK 文件的签名就能够了,从而一方面更严苛的避免了 APK 被篡改,另一方面也不用加压缩后对全部文件进行校验,从而极大提高了安装速度。第二代签名向后兼容,使用新签名的 APK 能够安装到 <7.0 的系统上,但要求 APK 同时也进行 v1 的签名。
具体来讲,第二代签名将整个 APK 文件进行签名,并将签名信息保存在了 APK 文件的的尾部 Central Directory 的前边。它能够对第一三四,以及第二块除了签名部分的其余区域提供一致性保护。
X-Android-APK-Signed
属性,这样一来,支持 V2 签名的系统在回滚到 V1 签名的时候就会校验是否存在这个属性,若是存在,就会拒绝安装 APK,这一切都是创建在 *.SF 文件被 V1 签名保护的基础之上。
Android 9.0 引入了第三代签名机制,主要增长一个功能叫 APK key rotation. 意思是容许开发者在更新 APK 的时候更换签名。签名的主要机制跟 V2 实际上是同样的,只是从新设计了 APK Signing Block 的存储结构,以支持更换签名。这里就再也不细说,能够参考官方的 文档
下图是安装一个 APK 时,系统对三代签名校验的流程示意。
by @monkeyM