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 密钥泄露后的应急处理

如果不幸密钥泄露了,怎么办?

  1. 立即撤销证书:向证书颁发机构申请撤销旧证书
  2. 生成新密钥对:使用v3签名方案的密钥轮换功能
  3. 发布紧急更新:用新密钥签名,强制用户升级
  4. 通知用户:告知用户重新安装应用

我曾经处理过一次密钥泄露事件。当时团队把密钥上传到了公开的GitHub仓库,虽然只暴露了10分钟,但已经被爬虫抓取了。我们连夜发布了紧急更新,用新密钥重新签名,并强制所有用户升级。那之后,我们彻底改了密钥管理流程。

25.4 签名验证流程

系统在安装APK时,会按以下顺序验证签名:

  1. 检查是否包含v4签名(Android 11+),如果有,验证增量更新数据
  2. 检查是否包含v3签名,如果有,验证密钥轮换链
  3. 检查是否包含v2签名,如果有,验证整个APK的完整性
  4. 如果以上都没有,回退到v1签名验证

注意:v2和v3签名可以共存。系统会优先使用v3,如果v3验证失败,不会回退到v2。这是为了防止降级攻击。

25.5 知识体系总览

下面这张图总结了APK签名方案的核心知识结构:

APK签名方案知识体系 v1 签名 JAR签名 v2 签名 全文件签名 v3 签名 密钥轮换 v4 签名 增量更新 核心特性对比 保护范围: v1: 部分文件 v2/v3: 整个APK v4: 增量数据 密钥管理: v1/v2: 固定密钥 v3: 支持轮换 兼容性: v1: 全版本 v2: Android 7+ v3: Android 9+ v4: Android 11+ 密钥保护最佳实践 • 使用HSM或Android Keystore硬件存储 • CI/CD流水线集成密钥管理服务 • 定期轮换密钥(建议1-2年) • 密钥分割存储 • 泄露后立即撤销并轮换

25.6 实战建议

最后,给你几条我在实际项目中总结的建议:

  • 新项目直接用v2+v3双签名:兼容Android 7+,同时支持密钥轮换
  • v1能不用就不用:除非你的App需要兼容Android 6及以下设备
  • 密钥保护比签名方案更重要:再强的签名,密钥泄露了也是零
  • 定期审计签名配置:检查是否有未签名的文件或过期的证书

嗯,关于APK签名就聊到这里。记住一句话:签名是应用安全的第一道防线,但绝不是最后一道。把它做好,你的App就成功了一半。


公众号:蓝海资料掘金营,微信deep3321