25. 应用签名与验证:APK签名方案(v1/v2/v3/v4)、签名密钥保护
聊到Android安全,签名机制是绕不开的一环。说白了,APK签名就是应用的「身份证」和「防拆封条」。没有它,任何人都可以篡改你的代码,然后重新打包发布——想想就可怕。
我刚开始做安全时,总觉得签名就是个形式。直到有一次,我负责的一个金融类App被人在第三方市场「二次打包」了,用户登录信息被劫持,公司差点吃官司。嗯,从那以后,我对签名机制再也不敢掉以轻心。
25.1 签名方案演进:从v1到v4
Android的签名方案经历了四代演进。每一代都在解决上一代的问题,同时也带来了新的挑战。我们一个一个来看。
25.1.1 v1签名方案(JAR签名)
v1方案是最早的,基于JAR签名标准。它的工作方式是这样的:
- 对APK内的每个文件单独计算摘要,存入MANIFEST.MF
- 再对MANIFEST.MF签名,生成CERT.SF和CERT.RSA
- 签名信息存放在META-INF目录下
听起来挺合理对吧?但有个致命问题:v1签名不保护APK内的未签名文件。什么意思呢?
我举个例子。你在APK里放了一个图片文件,v1签名时没把它纳入MANIFEST.MF。攻击者就可以替换这个图片,然后重新压缩APK——签名验证依然能通过。因为v1只检查MANIFEST.MF里列出的文件,没列出的就不管。
我曾经在项目中遇到过,某个第三方SDK的AAR包用了v1签名,结果被人在assets目录里塞了恶意脚本。从那以后,我要求所有依赖库必须使用v2或更高版本签名。
25.1.2 v2签名方案(APK签名方案v2)
v2方案在Android 7.0引入,它彻底解决了v1的问题。核心思路是:对整个APK文件进行签名,而不是逐个文件。
v2签名将签名信息插入到APK的ZIP中央目录之前。验证时,它会检查APK文件的每一个字节——任何改动都会导致签名失效。
// v2签名验证的核心逻辑(简化版)
// 1. 找到APK中的签名块(位于中央目录之前)
// 2. 提取签名数据
// 3. 对整个APK文件(除签名块本身)进行哈希验证
// 4. 验证签名证书链
// 注意:v2签名会保护APK的完整性
// 任何字节的修改都会导致验证失败
我个人习惯是,新项目一律使用v2签名。v1已经过时了,能不用就不用。
25.1.3 v3签名方案(密钥轮换支持)
v3方案在Android 9.0引入,最大的亮点是支持密钥轮换。什么意思呢?
你想想看,一个App可能运营好几年。如果签名密钥泄露了怎么办?以前只能换包名重新发布,用户数据全丢。v3方案允许你在更新时使用新密钥签名,同时证明新密钥和旧密钥的继承关系。
v3签名块中包含一个证书列表,记录了密钥的轮换历史。验证时,系统会检查当前签名是否由合法轮换链中的某个密钥生成。
- 轮换时,旧密钥必须对新密钥进行签名授权
- 轮换是单向的,不能回退到旧密钥
- 每个App最多支持多次轮换,但建议控制在3次以内
25.1.4 v4签名方案(增量更新支持)
v4方案是Android 11引入的,专门为增量更新设计。它不替换v2/v3签名,而是作为补充。
v4签名生成一个独立的签名文件(.idsig),包含Merkle哈希树。这样,增量更新时只需要下载变化的部分,系统可以通过Merkle树验证增量数据的完整性。
说实话,v4方案在实际项目中用得还不多。但如果你做的是大型游戏或系统应用,增量更新能节省大量带宽,值得关注。
25.2 签名方案对比
我把四种方案的核心差异整理成了表格,方便你对比:
| 特性 | v1 | v2 | v3 | v4 |
|---|---|---|---|---|
| 引入版本 | Android 1.0 | Android 7.0 | Android 9.0 | Android 11 |
| 保护范围 | 仅保护列出的文件 | 整个APK | 整个APK | 增量更新数据 |
| 密钥轮换 | 不支持 | 不支持 | 支持 | 不适用 |
| 增量更新 | 不支持 | 不支持 | 不支持 | 支持 |
| 安全性 | 低 | 高 | 高 | 中(需配合v2/v3) |
| 兼容性 | 所有版本 | Android 7.0+ | Android 9.0+ | Android 11+ |
25.3 签名密钥保护
签名方案再强,密钥泄露了也是白搭。我见过太多团队把密钥放在代码仓库里,或者用弱密码保护——这简直是给攻击者送人头。
25.3.1 密钥存储最佳实践
- 使用硬件安全模块(HSM):对于企业级应用,密钥应该存储在HSM中,物理隔离
- Android Keystore系统:在设备端,使用硬件-backed的Keystore存储密钥,防止提取
- 密钥分割:将密钥拆分成多份,分别存储在不同位置
- 定期轮换:即使没有泄露,也建议每1-2年轮换一次密钥
我习惯在CI/CD流水线中集成密钥管理服务(如AWS KMS或Azure Key Vault)。构建时,流水线从密钥服务获取签名密钥,构建完成后立即销毁内存中的密钥副本。这样,开发者本地根本接触不到生产密钥。
25.3.2 密钥泄露后的应急处理
如果不幸密钥泄露了,怎么办?
- 立即撤销证书:向证书颁发机构申请撤销旧证书
- 生成新密钥对:使用v3签名方案的密钥轮换功能
- 发布紧急更新:用新密钥签名,强制用户升级
- 通知用户:告知用户重新安装应用
我曾经处理过一次密钥泄露事件。当时团队把密钥上传到了公开的GitHub仓库,虽然只暴露了10分钟,但已经被爬虫抓取了。我们连夜发布了紧急更新,用新密钥重新签名,并强制所有用户升级。那之后,我们彻底改了密钥管理流程。
25.4 签名验证流程
系统在安装APK时,会按以下顺序验证签名:
- 检查是否包含v4签名(Android 11+),如果有,验证增量更新数据
- 检查是否包含v3签名,如果有,验证密钥轮换链
- 检查是否包含v2签名,如果有,验证整个APK的完整性
- 如果以上都没有,回退到v1签名验证
注意:v2和v3签名可以共存。系统会优先使用v3,如果v3验证失败,不会回退到v2。这是为了防止降级攻击。
25.5 知识体系总览
下面这张图总结了APK签名方案的核心知识结构:
25.6 实战建议
最后,给你几条我在实际项目中总结的建议:
- 新项目直接用v2+v3双签名:兼容Android 7+,同时支持密钥轮换
- v1能不用就不用:除非你的App需要兼容Android 6及以下设备
- 密钥保护比签名方案更重要:再强的签名,密钥泄露了也是零
- 定期审计签名配置:检查是否有未签名的文件或过期的证书
嗯,关于APK签名就聊到这里。记住一句话:签名是应用安全的第一道防线,但绝不是最后一道。把它做好,你的App就成功了一半。
公众号:蓝海资料掘金营,微信deep3321