Android 签名机制
前言
Android 签名机制伴随 Android 一起诞生,本片文章主要记录签名机制相关内容,主要包括以下几个方面:
- 为什么需要进行签名?
- 签名是什么?
- 如何进行签名?
- Android 签名有哪些版本?
为什么需要签名
一个应用从开发到上线再到用户手机上,经过了一个漫长的流程,而在这个流程中,一个 Apk 是很可能被其他人进行篡改。为了让整个流程中保证数据的安全性,需要通过签名机制进行安全性的校验。
签名是什么
签名是摘要和非对称密钥加密结合的产物。其中摘要是内容的一个指纹信息,内容的任何改变会导致摘要改变,签名就是对这个摘要进行加密的结果。
来看一下目前正在做的一个项目的 Debug 签名信息:
➜ overlord keytool -printcert -file META-INF/CERT.RSA
所有者: C=US, O=Android, CN=Android Debug
发布者: C=US, O=Android, CN=Android Debug
序列号: 1
有效期开始日期: Mon Dec 05 15:14:48 CST 2016, 截止日期: Wed Nov 28 15:14:48 CST 2046
证书指纹:
MD5: 76:B8:58:52:88:88:9C:66:6E:05:9D:33:14:93:26:97
SHA1: 3E:F7:42:EA:96:8B:D7:BF:89:5C:01:07:55:E4:AD:1D:BF:D4:ED:08
SHA256: 4A:78:8F:DB:F8:D6:7A:C6:8E:33:77:F5:E5:0F:27:19:B9:92:CF:D1:62:7D:CB:8D:9F:93:08:10:57:F2:A2:0A
签名算法名称: SHA1withRSA
版本: 1
通过 keytool 打印的信息可以看到一些关键信息,譬如签名算法的名称,签名版本,MD5 公钥等等。其中 META-INF 是解压 APK 后的文件,CERT.RSA 里面就是 APK 的签名信息。
刚刚提到的摘要信息也可以通过 Android Studio 方便看到。首先用 Android Studio 直接打开一个 APK ,找到 META-INF 目录,再找到 MANIFEST.MF 文件,点击便可以在 Android Studio 的内容窗口看到所有资源文件的摘要信息,其中一些片段如下:
Manifest-Version: 1.0
Built-By: Generated-by-ADT
Created-By: Android Gradle 3.4.2
Name: AndroidManifest.xml
SHA-256-Digest: I5bLtlYSFgHvPMYEEoXzD6QNxeRc1KXPCewj5jsMlSU=
Name: META-INF/android.support.design_material.version
SHA-256-Digest: f4VSLLX1VMgt9KN5N/I2LD4or1VKuL/adDaslosbgGs=
如何进行签名
通过 Android Studio 进行签名
新建一个工程之后,点击 《Build》->《Generate Signed Bundle / APK…》,将会看到如下确认框:

选择 APK ,然后点击 Next ,如下图所示:

key store path
简单说明下 keystore 文件,它包含了开发者信息以及用来非对称加密的公钥和私钥。如果之前使用 keytool 工具创建过了,可以使用之前的,否则可以直接通过 Android Studio 生成一个新的。这里我们点击 《Create new…》,将会看到如下对话框:

填好上述信息之后,确认创建时会有一个 Warning 提示如下:

按照提示,在命令行中输入以下命令:
keytool -importkeystore -srckeystore jerome -destkeystore jerome.jks -deststoretype pkcs12
按照提示输入目标密钥口令和源密钥库口令以及 key 的密码后,提示如下:
keytool 错误: java.lang.Exception: 目标 pkcs12 密钥库具有不同的 storepass 和 keypass。请在指定了 -destkeypass 时重试。
按照输出提示指定 -destkeypass 后如下:
keytool -importkeystore -srckeystore jerome -destkeystore jerome.jks -destkeypass sample -deststoretype pkcs12
转换完成。
然后继续上面的流程,填完其他项,如下图所示:

下一步选择 Build Variants 和 Signature Versions 之后 Android Studio 就开始打包签名了。
此时发现签名会失败,错误信息如下:
Get Key failed: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
Google 一圈儿没有发现什么有用信息,于是选择忽视之前的 Warning ,用没有转换为 pkcs12 的 keystore 进行签名,发现没有问题。
利用 apksigner 手动签名
先打出一个未被签名的 APK ,最简单的做法是,如果没有在 build.gradle 中配置签名信息的话,通过 Android Studio 打出一个 Release 包:
./gradlew assembleRelease
生成的 APK 文件名为 app-release-unsigned.apk ,此时通过 adb install 命令安装的话,会提示:
Failed to install xxx/app-release-unsigned.apk: Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Package /data/app/vmdl1079879915.tmp/base.apk has no certificates at entry AndroidManifest.xml]
接下来进行签名操作,先将 APK 文件和之前生成的 keystore 文件放在同一个目录,然后执行以下命令:
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore jerome app-release-unsigned.apk sample_key
输入密码之后即可完成签名。现在将文件改名为 app-release-signed.apk ,现在执行 adb install app-release-signed.apk 即可安装应用了。
Android 有哪些版本
在前面输出签名信息的例子中,最后一行看到一个版本信息:版本: 1 。Android 目前有三种签名版本。
v1 版本签名
Android 在此版本的签名工具有两种:
- jarsigner 这是 JDK 自带的签名工具,使用 keystore 文件进行签名,生成的签名文件默认使用 keystore 的别名命名。在上面的例子中,自己通过 jarsigner 签名的 apk 文件,其 META-INF 文件夹下生成三个文件为
SAMPLE_KEY.SF,MANIFEST.MF,SAMPLE_KEY.RSA。 - signAPK 这是 Android SDK 提供的专门给 Android 应用的签名工具,使用 pk8,x509.pem 文件进行签名,其中 pk8 是私钥文件,x509.pem 是公钥文件,生成的签名文件统一使用 CERT 命名。
选取一个 Android Studio 签名后的 APK ,通过 Android Studio 打开,总是能在 META-INF 目录下看到一下三个文件:
MANIFEST.MF这个在文章开头也展示过,它保存的内容其实是 APK 中文件的摘要信息。签名时会遍历 APK 中的所有条目,如果是文件,就用 SHA1/SHA256 摘要算法提取该文件的摘要后进行 Base64 编码,作为SHA1-Digest属性的值写到MANIFEST.MF文件的一个块中。CERT.SF与 MANIFEST.MF 性质相同的文件,只不过内部存储的是 MANIFEST.MF 的摘要信息。其中SHA1-Digest-Manifest-Main-Attributes是 MANIFEST.MF 头部块的摘要信息;SHA1-Digest-Manifest是 MANIFEST.MF 整个文件的摘要信息;SHA1-Digest是 MANIFEST.MF 中各个条目的摘要信息。CERT.RSA用私钥计算 CERT.SF 文件的签名,然后将签名和包含公钥信息的数字证书一同写入到 CERT.RSA 文件中保存。
综上,整个签名过程如下:

v2 版本签名
从 Android 7.0 开始,Android 支持全新的 v2 版本签名模式。回顾 v1 版本主要有以下问题:
- 速度慢:需要遍历所有的 APK 文件,无论是签名还是安装验证的时候都很耗时间。
- 签名程度不够:META-INF 目录用来存放签名,该文件本身不进行签名,那么它可以用来存放文件而对签名来说没有任何感知。 为了解决上述问题,v2 签名在原来的 APK 文件块中增加了一个新的签名块,里面存储了签名、摘要、签名算法、证书链等信息,直观来看入下图所示:

v2 中摘要的计算方式如下:
- 将 APK 文件中 ZIP 条目内容、ZIP 中央目录、ZIP 中央目录结尾按照 1MB 大小分割成一些小块。
- 计算每个小块的摘要信息,数据内容是 0xa5 + 块字节长度 + 块内容。
- 计算整体的数据摘要,数据内容是 0xa5 + 数据块的数量 + 每个数据块的摘要内容。
v2 的验证过程如下:

可以看到,v2 签名机制和 v1 是可以同时存在的,Android 7.0 以下依然走的是 v1 版本。
v3 版本签名
从 Android 9.0 开始,在 v2 的基础上增加了 密钥轮转 机制,是 APK 在更新过程中能更改其密钥,且 v3 在 APK 签名分块中添加了有关受支持的 SDK 版本和 proof-of-rotation 信息。
参考
https://source.android.google.cn/security/apksigning/v2?hl=zh-cn https://source.android.google.cn/security/apksigning/v3?hl=zh-cn https://blog.csdn.net/freekiteyu/article/details/84849651