记一次安装包签名问题的处理

2,427 阅读6分钟

在一个新的版本发布后,领导过来说APP没办法更新,提示签名缺失。之后便有了现在的故事。

首先理一下事情的经过

出现问题后,第一时间是自己试试,果然没法安装。然后怀疑最初打的包可能就存在问题,便重新打了一个包发现能够安装。接着怀疑是下载的安装包损坏,遂那两个安装包比较MD5码发现的确不一样,我认为在安装包上传到服务器或下载用户手机上的时候出现了问题,至此因为散养式的交接以及群中后端开发一直没提渠道打包相关的内容导致我没能准确地排查问题。

在我多次向后端说明我上传的安装包没有问题之后,后端才向我询问打包工具的版本以及packer-ng这个东西。之前没有需求,所以并没有了解过渠道打包的相关概念,趁此机会了解一下Android签名以及相关的内容。

各种签名版本

直到目前还是预览版的Android 11,签名总共有4个版本,每个版本都有自己的特色。签名的作用就是保证APK的可靠性,保证用户安装的版本是开发者发布的版本,而且不支持版本降级。关于各种签名的具体实现,一方面网络上相关的博文很多,另一方面虽然有在OneNote记了相关笔记但是又长又臭,只适合自己看,这里就只说个大概。

  • V1

    这种签名方式的签名文件存放在安装包的META-INF文件夹下,而且该文件夹是不被签名保护的。签名生成的方式是遍历APK中的文件并提取信息摘要生成MANIFEST.MF,然后对MANIFEST.MF做二次摘要生成CERT.SF文件,最后用该文件和私钥计算签名并和包含公钥信息的证书一起写入CERT.RSA文件。安装的时候会遍历文件进行校验,所以安装速度较慢。

  • V2

    V2签名相较于V1是个全新改版,从Android7.0开始引入。它能够加密全部文件并将签名写在APK扩展的一个块中。V2签名会将APK除签名块之外的其他区域按1MB的大小进行分割并计算信息摘要,然后用这些摘要进行二次摘要后与数字证书和其他的一些属性写入到签名块中。由于不在是以单个文件进行校验,所以安装速度上会比V1要快。

  • V3

    V3签名从Android9.0开始引入,较于V2签名区别不大,在签名块中增加了新的区域用于签名轮转,保证旧签名过期或者私钥泄漏后够替换成新的签名。

  • V4

    从Android11.0开始引入,目前在Preview的文档中能看到相关的内容,整体结构和V2与V3相差不大。这个方案会在单独的文件中生成一种新的签名,主要用于APK的adb增量安装。

在大致理清Android各个签名版本后,说一下渠道打包。因为国内缺少统一的应用市场,应用市场渠道鱼龙混杂,所以需要分渠道打包,不同渠道的包携带不同的信息方便后期运营统计。在V1签名中,因为META-INF是不被签名保护的,所以渠道信息可以放进这个文件夹。而在V2签名中因为文件全部被保护了,所以把渠道信息写到签名块中。目前市面上主流的多渠道打包方案是腾讯的VasDolly和美团的Walle

继续排查

在了解签名相关的内容后,结合后端所说的只言片语(也不指望能多说多少,毕竟他自己也没咋清楚),可以断定APK在上传后被写入渠道信息和被重新签名了,并且在了解签名的过程中也知道了apksigner这个工具。在使用apksigner验签后报出错误签名块长度异常,接着去翻了packer-ng的issue,看到一条#150相同信息的报错,但是我的错误信息是using APK Signat ure Scheme v2,而不是using APK Signat ure Scheme v3。说实话这里被带着跑了,一直以为是因为V3版本的签名导致系统开始验证签名块的长度,但是使用29.0.3版本的apksigner验证签名的时候提示并不支持V3签名。

接着翻了Walle的issue,看到这条#331issue,Gradle 4.0.0导致安装包签名注入渠道信息后出现了签名块长度验证,并且他的报错也是using APK Signat ure Scheme v2,他在回退Gradle版本后就正常了。回想自己操作,的确也升级了Gradle到4.0.0,所以可以确定是Gradle的版本升级导致打出来的包会导致V2签名也会进行签名块长度的验证,找到问题就可以着手解决了。

解决问题

解决这个问题有两个方法,一个是回退Gradle的版本,还一个是升级工具保证能够支持签名块长度验证。首先尝试了回退Gradle的版本,但是上山容易下山难,项目历史原因冲突较多,Gradle版本回退导致了很多库冲突和资源冲突,因为项目需要紧急修复这个问题,这些冲突不是一时半会儿能够解决的,暂时放弃这个方案。然后就换一个工具,packer-ng是不能用了,作者已经很久没有更新了,有其他开发者在issue中有提到Walle解决了相关的问题,然后转向Walle,同时也关注了一下VasDolly

有新的思路就会有新问题,这两个框架都不支持Gradle 4.0.0。这时候再回顾一下什么是渠道包,就是在签名块中添加渠道信息,也就是说只要一个能往APK中注入渠道信息的工具就行了,而签名的编译打包签名工作完全不需要框架封装的gradle命令来负责,只需要用AndroidStudio自带的工具就能完成,毕竟在不太清楚gradle相关的内容而且盲目使用的话简直是一个噩梦。

最后在Walle issue #264中找到了修复了签名块验证的jar工具包,获取相关jar包然后手写了python脚本用来注入渠道信息和验签。公司的项目包的发布是通过后端管理的,所以直接通过后端管理界面上传包交由后端使用脚本注入渠道信息即可(话说原来的后端脚本因为不清楚渠道注入相关的东西,所以在拿到APK包后又签名了一遍在注入信息)。

记录给未来的我。