Android 应用签名

Android 应用签名是应用打包过程的重要步骤之一,Google 要求全部的应用必须被签名才能够安装到 Android 操做系统中。Android 的签名机制也为开发者识别和更新本身应用提供了方便。android

数字摘要 Digital Digest

数字摘要主要做用是将任意长度的消息使用单向 HASH 算法摘要成一串固定长度的密文。经常使用的 HASH 算法包括 SHA-1, SHA-256, MD5 等等,MD5 中的 MD 就是 Message Digest 的缩写。数字摘要有时也被称为数字指纹,消息摘要等等,其实表达的都是一个意思。它有如下三个特色:git

  • 输出长度固定 例如 MD5 算法摘要信息有 128 比特,而 SHA-1 有 160 比特
  • 不考虑碰撞的状况下,只要输入原始数据不一样,摘要也不会相同。即便稍微改变输出,摘要就会变得彻底不一样。相同的输入也会产生相同的输出
  • 单向不可逆。从摘要没法恢复原始消息

Keystore 文件

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 相似一个钥匙仓库,里面有证书的全部者和发布者信息,包含了私钥和公钥信息,并设置了密码进行保护。安全

公共密钥系统 RSA

公钥和私钥都是公共密钥系统里的概念。最初全部的加密算法都属于对称加密,也就是说加密和解密使用的相同的密码,通讯双方如何安全沟通和保存密码,是这种加密方法的主要难题。难保没有猪队友。 而在公共密钥系统中,加密和解密使用的是不一样的密钥,分别称为公钥和私钥,公钥意思就是全部人均可以知道,私钥则只有全部者才持有,单从公钥没法在现有的计算能力下推导出私钥。这样一来就不存在沟经过程中泄露密钥的问题,只要私钥不泄露,通讯就一直是安全的。 公共密钥系统能够说是如今最最最重要密钥系统,是互联网的基石之一。 公共密钥系统能够用来加密,也能够用来签名。加密方案中,是不但愿别人知道个人消息,因此公钥用于加密信息,私钥用于解密信息;而签名方案中,是不但愿别人冒充我发消息,只有我才能发布这个签名,因此须要用私钥进行签名,公钥用于验证签名。bash

应用签名

咱们先来看看 Android 应用签名发生在构建的哪一步。app

在编译过程当中,编译器首先会将源代码和资源进行编译,生成 DEX 文件和一些编译后的资源文件,而后 APK Manager 会根据配置使用 keystore 文件进行签名,签名后才会将全部资源压缩到一个 ZIP 包里,这个 ZIP 包其实咱们安装的时候用的 APK 文件。能够看到签名是在构建基本完成的时候发生的。ide

签名过程

那 APK Manager 是如何使用 keystore 进行签名的呢?咱们一步一步看到底发生了什么。模块化

  1. 首先对编译后生成的全部的文件进行扫描,每一个文件生成一个数字摘要,保存在 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 这个文件的数字摘要究竟是如何计算出来的。工具

  • 第一步对文件进行 SHA-256 散列,获得一串 16 进制的散列值。
$ shasum -a 256 im_ic_keyboard_pressed.png
72a8ce8b7814bfd3b42017e02ce948254653070c823dc59b5c8b3fa3a4cc7d37 im_ic_keyboard_pressed.png
复制代码
  • 第二步咱们将其转换为二进制格式并进行 base64 编码
$ echo "72a8ce8b7814bfd3b42017e02ce948254653070c823dc59b5c8b3fa3a4cc7d37" | xxd -r -p | base64
cqjOi3gUv9O0IBfgLOlIJUZTBwyCPcWbXIs/o6TMfTc=
复制代码

能够看到跟咱们在 MANIFEST.MF 中看到的值是可以对上的。gradle

  1. 对 MANIFEST.MF 文件生成数字摘要,并写入 CERT.SF,这里有一个细节,除了对整个文件作 HASH 外,还会将文件分红多段计算 HASH 一样保存在 CERT.SF 文件中

  2. 计算 CERT.SF 的数字摘要,并使用 RSA 私钥进行加密,生成签名

  3. 将签名、公钥、哈希算法信息写入 CERT.RSA 文文件,并将这些文件添加到 APK 压缩包 META-INF 目录中

目前应用签名不须要申请可信的证书机构 (Certificate Authority) 签发的证书,开发者能够经过 keytool 来建立自签名的证书。

为何要签名

应用签名不能保证 APK 不被篡改,只是为了可以校验出 APK 是否被篡改。在系统安装过程当中,若是发现 APK 被篡改,安装就会失败。那系统是如何校验的呢?

  1. 系统取得已安装 APK 中保存的公钥,用它对新 APK 中的 CERT.RSA 保存的签名信息进行解密;对 CERT.SF 文件计算摘要,与上一步解密出来的信息进行比对,若是不一致说明 CERT.SF 被篡改,拒绝安装
  2. 对 MANIFEST.SF 文件计算摘要,与 CERT.SF 文件中的信息进行对比,若是不一致,则说明 MANIFEST.SF 文件被篡改,拒绝安装
  3. 对 APK 内全部其余文件计算数字摘要,若是文件没有出如今 MANIFEST.SF 或者摘要与 MANIFEST.SF 中包含的不相同,说明加入了新的文件或者文件被篡改,拒绝安装

整个校验过程当中,环环相扣,从 CERT.RSA -> CERT.SF -> MANIFEST.SF -> All Other Files,只要有一环失败,系统就会终止 APK 的安装.

若是给新版应用分配了新的签名文件,那就必须更改包名,这样系统才会认为是不一样的应用。不然安装就会失败,提示签名不一致。 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES

签名的其余用途

除了用于安装时校验应用,签名还有一些别的用途。

  1. 应用模块化。Android 容许相同签名的两个应用使用一样的 Linux UserId,这样一来两个应用就能够共享数据存储了。同时若是应用申请的话,两个应用还能够在同一个进程中运行。经过这种方式能够模块化部署应用,每一个模块也能独立的进行升级。猜测不少主题资源包可能就是经过这种方式来安装的。
<manifest
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:sharedUserId="com.meng.sharedappid"
        package="meng.mainuicomponents">
复制代码
  1. 共享代码数据。Android 提供了能够在使用一样签名的应用间共享代码的功能。另一个前提也是两个应用设置了使用一样的 shareUserId。可使用包名拿到兄弟 APP 的 Context,而后拿到 ClassLoader 加载兄弟 APP 的类,并能够实例化并经过反射来调用具体的方法。
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)
复制代码
  1. 用来声明安全级别。好比隶属同一个公司的多个应用实现共享登录功能,能够各自实现本身的 ContentProvider,向外提供访问本应用数据的接口,但这个接口须要限制不能被其余第三方的应用读取,经过限制安全级别为签名级别,系统就能保证只有相同签名的应用才能够访问到这个 ContentProvider。如下是在 AndroidManifest.xml 文件中的使用示例。
<!-- 声明权限供兄弟 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" />
复制代码

签名机制的演进

V1

第一代是基于 JAR 文件签名,它主要的缺陷是只保护了一部分文件,而不是对整个 APK 文件作保护。这是由于全部文件都不可能包含了自身的签名,由于它不可能为本身签名后再把签名信息保存到本身内部,这是一个鸡生蛋蛋生鸡的问题,由于这个问题的存在,第一代签名机制会忽略全部以 .SF/.DSA/.RSA 的文件以及 META-INFO 目录下的全部文件。 因此攻击者就能够解压缩后在 APK/META-INF 目录新增一个含有恶意代码的文件,而后再压缩成 APK,一样是能够覆盖安装正版应用的,这样一来好好的应用就会被杀毒软件标记为恶意软件,从而达到攻击应用的目的。 除了容易被攻击外,应用安装起来也比较慢,由于安装器在校验时须要解压计算全部文件的数字摘要,确认没有被恶意修改。

美团打渠道包的方法本质上就利用了这个第一代签名的漏洞,在 META-INF 目录下新建了一个包含 vendor 名称的文件,从而不须要从新编译,只须要解压缩 APK,添加文件,从新压缩就完成了一个渠道包的生成,速度很是快。

V2

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 签名保护的基础之上。

V3

Android 9.0 引入了第三代签名机制,主要增长一个功能叫 APK key rotation. 意思是容许开发者在更新 APK 的时候更换签名。签名的主要机制跟 V2 实际上是同样的,只是从新设计了 APK Signing Block 的存储结构,以支持更换签名。这里就再也不细说,能够参考官方的 文档 下图是安装一个 APK 时,系统对三代签名校验的流程示意。

相关文章
相关标签/搜索