Walle,是美团点评技术团队针对 Android Signature V2 Scheme 签名过的APK的一个多渠道打包工具

近来恰巧在学习Go,就想着把它“翻译”成Go,权当作练手,同时还能给“包管理后台”用用。

Walle 的工作原理

想要把一个工具用另一种语言实现,首先得明白它的原理。

其实原理官方已经解释的很详细了,这里就不再详细缀述,主要列出一些关键的点:

APK 签名的本质

APK 文件其实是一个 ZIP(Jar包),所谓签名其实就是对包或者包中的文件做签名

V1 其实就是 Jar 签名,“签名”要保护的是包中的文件不被修改,并不保护包本身(如ZIP Comment);V2 则是针对V1的缺陷,在V1的基础上将包本身视为Blob再做一次签名。

这就要求 V2 需要在不破坏、不影响 ZIP 文件格式(能被普通的ZIP工具解压),不污染V1签名正确性(APK能被老Andriod系统识别)的前提下写入到包中。
APK 签名验证过程
(图来自https://source.android.com/security/apksigning/)
故而需要针对性的在不伤害 ZIP 文件格式的前提下做一些“魔改”,将 V2 签名数据块插入到 ZIP 的 Central Directory 之前。
APK V2 签名之前与之后

V2 签名之后的APK

APK 被分为 4 部分:

  1. Contents of ZIP entries (从文件头开始直到 APK Signing Block)
  2. APK Signing Block
  3. ZIP Central Directory
  4. ZIP End of Central Directory

1、3、4的内容受到APK Signing Block 保护,准确的说是受 APK Signing Block 中的签名信息保护

APK Signing Block 格式

  • Signing Block 字节数 (不含自身计数) (uint64)
  • “ID-value” 对序列:
    • 此“ID-value” 长度 (uint64)
    • ID (uint32)
    • value (可变长度: “ID-value” 长度 - 4个字节)
  • Signing Block 字节数 (与前面的数值相同) (uint64)
  • magic “APK Sig Block 42” (16 个字节)

为向后兼容考虑,ID-value对设计成了可以存在多个。

V2 签名是数据块中的一个ID-value对,其ID0x7109871a,值即为“签名信息”(signed data)。

在验证 V2 签名过程中,会跳过验证器不认识的 ID

插入渠道信息

从上面的原理可以得到结论APK Signing Block中可以增加一个ID-value来存储渠道,且不会破坏原有签名信息,即毋须重签名做到插入渠道

实现

具体的代码已经放到了github上,此处就不一一罗列了 https://github.com/yrom/walle-go

其中绝大部分代码参考了 https://android.googlesource.com/platform/build/+/android-7.1.2_r27/tools/signapk (毕竟开头说的就是“翻译” <(▰˘◡˘▰)>)

关键代码在 src/walle下面,并基于包walle实现了一个命令行工具。

对 Go 的几点总结

以下都是我本人的意见,如有谬误,欢迎指出。

  1. 用 Go 写一个命令行工具挺方便的,编译速度快,执行效率也还不错(就我这个新手写的代码而言,应该还有优化空间)
  2. Go 的数值类型转换没有“隐式转换”这一说,需要手动强转。我认为对于有大量数值运算的代码来说会很不方便(也许有我不知道的解决方案)
  3. Go 同时有“函数式”(Functional)和“面向对象”(OO)两个编程思想的体现,个人觉得使用“手感”略高于 Java 8,但不如 Kotlin。它没有Class,也没有显式的implements关键字,对于我这样的新手来说会犯迷惑:这是什么类型,它为何可以这样用,难以置信!
  4. Go 不像Java,不给直接用系统线程,而是用了一个 goroutine 的实现,既类似“线程”而名字又像个“协程”。使用上形似Java中向线程池提交一个Runnable。
  5. 学会使用Go是一回事,深入理解Go又是另一回事了。。。

EOF.