借腾讯开源 VasDolly,谈谈 Android 签名和多渠道打包的原理!
APK 签名方案 v2 是一种全文件的签名方案,该方案能够对 APK 所有受保护的部分进行签名保护,从能能够发现它们被篡改。
在 APK 验证期间,v2 方案会将 APK 文件视为 Blob,并对整个文件进行签名检查。对 APK 进行的任何修改(包括对 ZIP 元数据的修改),都会使 APK 签名作废。
使用 APK 签名方案 v2 进行签名时,会在 APK 文件中,插入一个 APK 签名块,该分块位于“ZIP 中央目录”部分之前并紧邻该部分。在“APK 签名分块”内,v2 签名和签名者身份信息会存储在?APK 签名方案 v2 分块中。
该分块包含多个 "ID-值" 对,所采用的封装方式有助于更轻松地在 APK 中找到该分块。APK 的 v2 签名会存储为一个 "ID-值" 对,其中 ID 为 0x7109871a。
上图是签名前后,APK 文件结构的对比。可以看到在 v2 已签名的 APK 中,包含了 4 个部分。
ZIP 条目的内容。
APK 签名分块(APK Signing Block)。
ZIP 中央目录。
ZIP 中央目录结尾。
APK 签名方案 v2 负责保护第 1、3、4 部分的完整性,以及第 2 部分包含的 APK 签名方案 v2 分块中的?signed data
?分块的完整性。
第 1、3 和 4 部分的完整性通过其内容的一个或多个摘要来保护,这些摘要存储在?signed data
?分块中,而这些分块则通过一个或多个签名来保护。
第 1、3 和 4 部分的摘要信息,采用一种类似两级 Merkle 树的方式进行计算,所以效率上会提高很多。这个就比较复杂了,大家有兴趣再深入研究。
既然已经有了 v2 方案,我们是否可以完全舍弃 v1 方案,只使用 v2 方案呢?
从官方文档了解到,因为 APK 签名方案 v2 是在 Android 7.0 中引入的,为了使 APK 可以在 Android 6.0 以及更低的版本中正常安装,应该先使用 JAR 签名(v1 签名)对 APK 进行签名然后再使用 v2 方案对其进行签名。
而在实际测试中,这种仅使用 v2 方案不使用 v1 方案的情况,是可以编译通过的,并且在 Android 7.0 之上的设备上也是可以安装和运行,但是在 7.0 之下,因为不检测 v2 签名,而 APK 又不存在 v1 签名,在安装的时候,会提示没有签名的错误。
为了保证兼容性的问题,在 7.0 以上的设备中,也是兼容了 v1 签名的方案。所以 v2 签名方案,暂时并不是一个强制的方案。
优先级是:优先校验 v2 方案,没有或者不存在校验机制,则校验 v1 方案。
2.4 两种方案对比
到这里,我想应该对 v1 和 v2 签名方案应该已经有一定了解了。v2 签名既然是升级版,就是为了解决 v1 签名方案的一些问题。
1、效率问题
v1 方案,会在 /META-INF/MANIFEST.MF
文件中,记录所有需要校验签名的文件的数据摘要,并且这里生产数据摘要是依据压缩前的源文件,而在打包的过程中,这些文件又会被压缩。
所以在验证签名的过程中,需要先解压出原始文件,才能计算数据摘要从而验证它,而这个过程,会消耗更多的时间和内存。
v2 签名方案,是对 APK 文件本身进行数据摘要计算,也就是说它只计算一个文件(v1 会计算 Apk 内的很多文件),这样就不存在解压 APK 的操作,效率和内存都得到优化。
2、安全问题
v1 方案,它只会对 APK 内部,部分文件进行校验,并不会对 APK 的完整性进行校验。因此,签名后,我们依然可以修改 APK 文件,例如:美团的多渠道方案就是在 /META-INF/
目录下,添加一个空文件,使用文件名来标志渠道。
而 v2 签名是针对整个 APK 进行校验,所以对 APK 的任何改动,都无法通过 v2 签名的验证,这样安全性会更高。
关于签名校验的耗时上,主要影响的是安装耗时,这里有一份实验室数据(Nexus 6P、Android 7.1.1)可供参考。
差不多 2.x 倍的差距,v2 签名对 APK 的安装还是有不少提升的。
三、常见的多渠道方案
多渠道的需求点,在于同一个 App,需要有一些不同来区分它们,最常见的场景就是 Android 国内的市场,对不同的市场使用不同的渠道包,它们的代码是一致的,只是一个渠道号的数据不一样。这样区分的好处在于数据更清晰,我们知道那个渠道的用户是优质用户。
3.1 被废弃的方案
既然签名方案在升级,多渠道的方案也需要升级。曾经有一写可行的方案,基本上已经被废弃了。例如:Gradle Plugin 配置不同的 Flavors、利用 ApkTool 解包改数据再重新打包并重新签名。
这些方案都有一些局限性。例如 Gradle Flavors 方案,每个渠道都需要重新打包,非常的耗时,并且生成的渠道包 DEX 的 CRC 值都不一致,不利于我们使用热更新的方案;而 ApkTool 的方案,首先 解包→打包→重新签名 的过程同样耗时,其次在于,不稳定,可能升级了 Gradle Plugin 的版本之后,会导致解包失败。
所以我们在想,一个高效的多渠道打包方案,有几个关键单需要注意。
不能破坏签名。
不能重新打包。
读取信息,必须高效。
不破坏签名就限制了不能解包以及重新签名,势必对效率有所提高。
而除了 VasDolly 之外,市面上流传比较广的就是 美团 给出的 Walle 方案,这里就以它们的原理进行简单的讲解。
3.2 v1 签名下的常见多渠道方案
v1 下对签名的校验比较弱了,美团给出了完整的解决方案。前面也介绍过,就是在以及打包签名好的 Apk 中,向 /META-INF
目录下,写入一个空文件,以文件名来标识渠道号。
![](https://user-gold-cdn.xitu.io/2018/2/28/1
61dab4a56f13bc3?imageView2/0/w/1280/h/960/ignore-error/1)
首先,使用这样的方案,并不会破坏 v1 签名,所以效率会很高。
而在腾讯的 VasDolly 中,其实是向 Apk 文件的 EOCD 部分,增加渠道的信息。
APK 文件本质上是一个 ZIP 包,而 EOCD 就是上图所示的第三部分,并且这部分是不被 v1 签名校验的,可以用来记录我们的渠道信息。
3.3 v2 签名下的多渠道方案
v2 方案下,其实腾讯 VasDolly 和美团的 Walle 方案,并没有什么区别,因为验证更强了,大家可操作的区域就只有那么多了。
前面也提到过,使用 v2 签名后的 APK 文件结构中,插入了一个 APK Signing Block 的签名块。
在这个 APK 文件结构下,只有 块 2 ,也就是记录 APK v2 签名信息的区域,不是全部参与 v2 签名的校验,所以大家都在这部分做文章。
APK Signing Block 中包含了多个 "ID-值" 的键值对,而 v2 签名的信息,会记录在 ID 为 0x7109871a
中,而不会校验其他 ID 下的值。
因此,基于 v2 签名的多渠道方案就这样诞生了:在 APK 签名块中添加一个额外的 ID-值,用于记录渠道信息。
四、可商用的多渠道打包方案
在 VasDolly 开源之前,市面上支持 v2 签名的多渠道打包方案,就属美团的 Walle 了,下面简单比对一下它们的优缺点。
评论