Android 应用签名是应用打包过程的重要步骤之一,Google 要求全部的应用必须被签名才能够安装到 Android 操做系统中。Android 的签名机制也为开发者识别和更新本身应用提供了方便。android
数字摘要主要做用是将任意长度的消息使用单向 HASH 算法摘要成一串固定长度的密文。经常使用的 HASH 算法包括 SHA-1, SHA-256, MD5 等等,MD5 中的 MD 就是 Message Digest 的缩写。数字摘要有时也被称为数字指纹,消息摘要等等,其实表达的都是一个意思。它有如下三个特色:git
Android 签名须要用到一种后缀名为 keystore 的文件。在打 Debug 包的时候,若是没有在 build.gradle 文件中指定的话,Gradle 就会自动为咱们生成一个 keystore文件,保存在系统用户根目录 .android 文件夹内,名称为 debug.keystore. 咱们以它为例看看 keystore 文件包含了什么内容。 经过 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 相似一个钥匙仓库,里面有证书的全部者和发布者信息,包含了私钥和公钥信息,并设置了密码进行保护。安全
公钥和私钥都是公共密钥系统里的概念。最初全部的加密算法都属于对称加密,也就是说加密和解密使用的相同的密码,通讯双方如何安全沟通和保存密码,是这种加密方法的主要难题。难保没有猪队友。 而在公共密钥系统中,加密和解密使用的是不一样的密钥,分别称为公钥和私钥,公钥意思就是全部人均可以知道,私钥则只有全部者才持有,单从公钥没法在现有的计算能力下推导出私钥。这样一来就不存在沟经过程中泄露密钥的问题,只要私钥不泄露,通讯就一直是安全的。 公共密钥系统能够说是如今最最最重要密钥系统,是互联网的基石之一。 公共密钥系统能够用来加密,也能够用来签名。加密方案中,是不但愿别人知道个人消息,因此公钥用于加密信息,私钥用于解密信息;而签名方案中,是不但愿别人冒充我发消息,只有我才能发布这个签名,因此须要用私钥进行签名,公钥用于验证签名。bash
咱们先来看看 Android 应用签名发生在构建的哪一步。app
在编译过程当中,编译器首先会将源代码和资源进行编译,生成 DEX 文件和一些编译后的资源文件,而后 APK Manager 会根据配置使用 keystore 文件进行签名,签名后才会将全部资源压缩到一个 ZIP 包里,这个 ZIP 包其实咱们安装的时候用的 APK 文件。能够看到签名是在构建基本完成的时候发生的。ide
那 APK Manager 是如何使用 keystore 进行签名的呢?咱们一步一步看到底发生了什么。模块化
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 中看到的值是可以对上的。gradle
对 MANIFEST.MF 文件生成数字摘要,并写入 CERT.SF,这里有一个细节,除了对整个文件作 HASH 外,还会将文件分红多段计算 HASH 一样保存在 CERT.SF 文件中
计算 CERT.SF 的数字摘要,并使用 RSA 私钥进行加密,生成签名
将签名、公钥、哈希算法信息写入 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 的前边。它能够对第一三四,以及第二块除了签名部分的其余区域提供一致性保护。
在计算签名的时候,会将这些部分的数据切割成 1MB 大小的 CHUNK,分别计算签名,而后汇总后再计算一个总签名,这么作的主要目的是为了并行计算,加快速度。
为了不攻击者在 7.0 以上系统中绕过 v2 签名机制(好比删除 APK Signing Block?),v2 签名要求若是 APK 同时提供了 V1 签名的话,须要在 META-INF/*.SF 文件中增长一行 X-Android-APK-Signed 属性,这样一来,支持 V2 签名的系统在回滚到 V1 签名的时候就会校验是否存在这个属性,若是存在,就会拒绝安装 APK,这一切都是创建在 *.SF 文件被 V1 签名保护的基础之上。
Android 9.0 引入了第三代签名机制,主要增长一个功能叫 APK key rotation. 意思是容许开发者在更新 APK 的时候更换签名。签名的主要机制跟 V2 实际上是同样的,只是从新设计了 APK Signing Block 的存储结构,以支持更换签名。这里就再也不细说,能够参考官方的 文档 下图是安装一个 APK 时,系统对三代签名校验的流程示意。