Android的APK两种签名

5,354 阅读7分钟
原文链接: blog.cyning.cc

前几天在研究APK瘦身的相关知识,发现有个很有意思的知识点就是apk的签名 – APK Signature Scheme v2,虽然出来了一段时间,但是这方便真是没做太多的关注,趁着十一刚过的热乎劲撸起袖子干一波。

准备

目前为止,android通用的打包过程时使用的签名工具有两套–jarsigner和apksigner。其中apksigner也就是刚才我们提到的APK Signature Scheme v2,两者有什么关系,为什么要使用新的签名机制呢?这是我们这篇文章介绍的重点。

在了解签名之前我们需要了解几个知识点:

  • 数字签名
  • ZipAlign
  • keystore

数字签名 – Signature

  • 数字签名就是信息的发送者用自己的私钥对消息摘要加密产生一个字符串,加密算法确保别人无法伪造生成这段字符串,这段数字串也是对信息的发送者发送信息真实性的一个有效证明。
  • 数字签名是 非对称密钥加密技术 + 数字摘要技术 的结合。
  • 数字签名技术是将信息摘要用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的信息摘要,然后接收者用相同的Hash函数对收到的原文产生一个信息摘要,与解密的信息摘要做比对。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改;不同则说明信息被修改过,因此数字签名能保证信息的完整性。并且由于只有发送者才有加密摘要的私钥,所以我们可以确定信息一定是发送者发送的。
    我们的APK就是通过这种数字签名技术保证我们APK的安全。

ZipAlign

zip对齐,因为APK包的本质是一个zip压缩文档,经过边界对齐方式优化能使包内未压缩的数据有序的排列,从而减少应用程序运行时的内存消耗 ,通过空间换时间的方式提高执行效率(zipalign后的apk包体积增大了90KB左右)。

切换目录到SDK的build-tools目录下(例如 SDK/build-tools/25.0.2/),执行:

zipalign -v 4 infile.apk outfile.apk

keystore

keystore是我们打包过程中必须要填写的内容,这个类似我们代表我们开发者信息的一个『证书』,这个证书类似开发者的”身份证”,它是唯一的,所以只要一个app有这个证书,在应用市场上就可以保证这个app不会被别人冒充,也可以作为一个开发者的凭证(只要keystore的密码账号不被泄露)。
具体内容可以参考:Android Keystore漫谈.
我们可以通过jdk/bin/下的keytool工具来创建自己的keystore:

keytool -genkeypair -alias "cyning" -keyalg "RSA" -keystore "cyningfile.keystore"

创建一个别名为cyning的证书,该证书存放在名为cyningfile.keystore的密钥库中,若cyningfile.keystore密钥库不存在则创建。
现在更多人是通过Android Studio的工具来创建自己的keystore。

参数说明:

-genkeypair:生成一对非对称密钥;
-alias:指定密钥对的别名,该别名是公开的;
-keyalg:指定加密算法,本例中的采用通用的RAS加密算法;
-keystore:密钥库的路径及名称,不指定的话,默认在操作系统的用户目录下生成一个”.keystore”的文件

查看这个keystore:
keytool -list -keystore cyningfile.keystore

APK的签名

在V2签名出来之前我们都是通过jarsigner签名,而后才有APK Signature Scheme v2.他们有很大的不同。

META-INF下签名三兄弟

所谓签名三兄弟,实际上就是META-INF文件夹下的MANIFEST.MF、CERT.SF、CERT.RSA三个文件。
它会先通过SHA1算法生成这些文件(png,dex等)的信息摘要,而后会生产MANIFEST.MF。

jarsigner

jarsigner是由JDK提供的一个签名工具,JDK/bin/jarsigner(jarsigner,windows上是exe文件).
通过这个工具也可以给我们的APK进行签名,在APK Signature Scheme v2出来之前都是采用这种方式。
我们可以使用如下命令:

jarsigner -verbose -keystore test.keystore -signedjar -signed.apk unsigned.apk 'test.keystore'

对照签名前后,META-INFO文件的变化:

签名前
签名后

就会发现签名后多了两个文件CERT.SF、CERT.RSA,同时MANIFEST.MF会多了文件和对应的SHA1算法下的摘要。
MF实际上会列出文件解压后的所有文件除了META-INF下所有文件的签名。
SF则是MF文件的摘要信息以及.MF文件当中每个条目在用摘要算法计算得到的摘要信息并用base64编码保存;CERT.SF文件则存放证书信息,公钥信息,以及用私钥对.SF文件的加密数据即签名信息,这样保证了每个文件的完整性,但都是基于apk解压后的所有文件。

在安装APK时,会先检查CERT.SF文件,确认签名正确,而后检查SF文件,再通过SF去检查apk文件下各个文件,这样一步步环环相扣确保了每个文件都是有效的,确保了文件的完整和安全性,但是对于好事者还是发现了其中的漏洞,美团有个方案就是通过在META-INF下添加一个空文件来代表渠道,这个空文件不会被检查,他和任何文件没有关联(CERT.SF、CERT.RSA和MANIFEST.MF他们是相关关联的)。

这样是没有问题的,什么对于apk文件ZipAlign处理后他们的压缩产物是一致的,所以在安装apk时,也是可以通过,但是这样真的安全么?
答案是否!
我们可以通过这些不检测文件中存放不安全的代码,同时每次安装apk都需要解压apk文件来进行校验,耗时,也是个耗电的过程。

于是乎,在Android Build Tools从24.0.3版本引入的一个新的apk文件签名工具apksigner,也就是google官网上说的
Signature Scheme v2翻译过来过来就是APK签名方案v2.

apksigner

apksigner是基于真个apk二进制文件进行校验的,即使你使用了ZipAlign对齐后,二进制文件发生了改变,就认为两个apk不一样,实际上也就是两个不同的文件。
官方解释是在apk文件的Central Directory前面插入了一个apk签名块用来存放apk的签名信息,来确保apk签名的安全性,如图:

这样apk就等于是是有了四块:

google建议: APK 签名方案 v2 是在 Android 7.0 (Nougat) 中引入的。为了使 APK 可在 Android 6.0 (Marshmallow) 及更低版本的设备上安装,应先使用 JAR 签名功能对 APK 进行签名,然后再使用 v2 方案对其进行签名。
在android studio的Gradle Plugin 2.2以及之上版本的插件当中,默认是v2开启的,需要的话可以自定义关或者开:

signConfig {
    v1SigningEnabled false
    v2SigningEnabled false
}

其中apk签名块也是个的结构如下:
apk签名块
美团又一次利用了规则,在ID-Value的加入一个渠道ID-Value(apk不会校验id,value的有效性)。
其文章思路参考: 新一代开源Android渠道包生成工具Walle .

所以,和jarsign相比,apksign签名更安全,其打包生成的apk也更小,赶快动手试试吧。
使用么就很简单啦:

apksigner sign --ks keystore.jks |
  --key key.pk8 --cert cert.x509.pem
  [signer_options] app-name.apk

更多工具可以参考谷歌官方文档.
总结下:jarsigner签名是jdk提供的签名工具,它是针对apk文件压缩后的文件进行的完整和安全性校验,而apksign也是通过对apk二进制文件的校验,也更安全。