2. Android签名机制

签名这玩意儿,说白了就是给 APK 打个“防伪标签”。你想想看,Google Play 商店里那么多 App,凭什么相信你下载的微信是真微信?靠的就是签名。

但 Android 的签名机制不是一步到位的。从最早期的 JAR 签名,到后来的 v1、v2、v3、v4,每一代都在解决上一代遗留的问题。我在跟一些开发者聊天时发现,很多人只知道“哦,要用签名”,但对这几代的区别一脸懵。

没关系,我今天一次性给你讲清楚。

签名的本质是什么?

先问一个最简单的问题:签名到底在保护什么?

  • 完整性:APK 里的文件有没有被篡改?
  • 身份验证:这个 APK 到底是谁发布的?
  • 不可否认性:开发者不能说自己没发过这个包。

这三条,是签名机制的核心目标。后面的每一代方案,都是围绕这三个点在做改进。

我记得有一次在做合规检测时,客户问我:“我只要签名了不就行了吗?为什么还分 v1、v2?”嗯,你听我慢慢说。

核心认知:签名 ≠ 加密。签名是为了“验证谁写的 + 内容没变”,而不是为了“让内容不被看到”。别搞混了。

JAR 签名:最原始的方案

在 Android 还没出生之前,Java 世界就已经有了 JAR 签名方案。Android 最早期的签名机制,直接沿用了这套方案。

JAR 签名的核心思路很简单:

  1. 计算每个文件的哈希值。
  2. 把哈希值放进 META-INF/MANIFEST.MF 文件里。
  3. 再对 MANIFEST.MF 做一次哈希+签名,生成 .SF 和 .RSA 文件。
  4. 验证时,逐个文件检查哈希。

这套机制有两个致命问题,我在项目里踩过坑:

  • 验证粒度太粗:它只验证文件内容,不验证文件结构。
  • 容易被绕过:攻击者可以解开 APK,修改或删除 META-INF 里的签名文件,再重新打包。只要签名文件被删掉,系统就认为没签名,反而忽略了篡改。
踩坑记录:我曾经帮一个客户做 APK 完整性检测,发现他们的 App 在 Android 4.4 以上运行正常,但在 Android 4.4 以下被篡改。原因就是老设备只认 JAR 签名,攻击者把签名文件删掉后,旧系统直接不验证了。

APK 签名方案 v1:JAR 签名的 Android 化

v1 本质上就是 JAR 签名。但它做了一些 Android 特定的适配。

它的工作流程是这样的:

  1. 打包后,对所有未签名文件计算 SHA1 或 SHA256 哈希。
  2. 写入 META-INF/MANIFEST.MF。
  3. 对 MANIFEST.MF 签名,生成 META-INF/CERT.SF 和 CERT.RSA。
  4. 安装时,解析这些文件,逐文件验证。

v1 的问题在于:它只能保护 ZIP 条目内部的每个文件,但 ZIP 条目本身的顺序、额外字段、中央目录,这些它都不管。

实战技巧:如果你在做逆向分析,v1 签名的 APK 可以轻松地修改再重签。但 v2 以后就不行了。所以我个人会建议:发布到应用商店的包,一定要上 v2 或更高版本的签名。

APK 签名方案 v2:真正的突破

v2 是 Android 7.0(API 24)引入的。它解决了一个核心矛盾:签名应该保护整个 APK,而不是其中的每个文件。

v2 的设计思路完全不同:

  • 签名信息被放在 APK 文件的一个特殊区块中(APK Signing Block)。
  • 这个区块插在 ZIP 中央目录之前。
  • 验证时,对整个 APK 的 “字节范围” 做签名。

换句话说:v2 签名保护的是 APK 文件本身,而不是 文件内容的集合。

关键区别:v1 签名保护的是“文件内容”,v2 签名保护的是“APK 文件”。一个从里往外保护,一个从外往里保护。v2 的安全性高得多。

我们来看一下 v2 的签名区块结构:

APK 文件布局(带 v2 签名):
+-------------------------------+
| ZIP 条目(文件内容)           |
| ...                            |
+-------------------------------+
| APK Signing Block              |  ← v2 签名放在这里
|   - 签名者序列                 |
|   - 摘要及证书                 |
+-------------------------------+
| ZIP 中央目录                   |
| ...                            |
+-------------------------------+
| ZIP 结束标记                   |
+-------------------------------+

v2 的优点很明显:

  • 篡改 APK 后,签名立刻失效。
  • 验证速度更快(不需要逐个文件解压算哈希)。

但 v2 也不是完美的。我遇到过一个问题:如果签名区块被破坏,整个 APK 都没法安装了。这对于做增量更新OTA的团队来说,是个灾难。

APK 签名方案 v3:密钥轮转

v3 是在 Android 9.0(API 28)引入的。它最大的创新是:支持密钥轮转。

什么叫密钥轮转?说白了就是:你的私钥丢了,或者你想换一把更强的签名证书,不用重新发布一个全新的 App 了。

v3 的签名区块里,包含了一个 签名者列表。每个签名者需要声明自己的证书,以及 指向上一代签名者的证明(Proof-of-rotation)。系统在验证时,会沿着这条链往上查,确认这个新签名者有合法的继承权。

个人建议:v3 的密钥轮转非常实用,但实际项目中我很少看到有人用。原因很简单——Google Play 的密钥管理服务已经帮你做了这件事。但如果你做的是企业内部分发,或者没有用 Google Play,那 v3 就是个好东西。

APK 签名方案 v4:增量更新的孪生兄弟

v4 是 Android 11(API 30)新增的方案。它专为 ADB 增量更新(Incremental Installation) 服务。

v4 签名的特点:

  • 它不是一个完整的签名方案,而是 v2/v3 的补充。
  • v4 签名以独立文件存在(.idsig)。
  • 它包含 Merkle树根哈希,允许系统只验证文件的一部分。

增量更新允许用户只下载 APK 中的部分内容就开始安装。v4 签名的存在,就是为了让这种“边下边装”的场景依然能保证安全性。

一句话总结:v1 管文件,v2 管 APK,v3 管密钥轮转,v4 管增量更新。彼此不是替代关系,而是叠加关系。

SVG 图:四代签名方案演进对比

Android 签名方案演进 v1 (JAR签名) Android 1.0 v2 Android 7.0 v3 Android 9.0 v4 Android 11+ 特性 逐文件验证 只保护 ZIP 条目 可被绕过删除 特性 APK Signing Block 保护整个 APK 文件 验证快、安全性高 特性 支持密钥轮转 签名者链 兼容 v2 特性 增量更新专用 Merkle树根哈希 需配合 v2/v3 兼容性说明 v1 → v2 → v3 → v4 逐代兼容 高版本 Android 仍支持低版本签名 建议:同时使用 v1 + v2 + v3 三签

四代签名方案对比表

维度 v1 (JAR) v2 v3 v4
最小 API 1.0 24 (7.0) 28 (9.0) 30 (11)
签名位置 META-INF/ APK Signing Block APK Signing Block 外部 .idsig
保护范围 ZIP条目 整个 APK 整个 APK 增量部分
密钥轮转 不支持 不支持 支持 不支持
验证速度 慢(逐文件) 快 快 中等
能否单独使用 能 能 能 不能

实际项目中的签名策略

我个人的经验是:不要纠结“到底用哪个”。

现在的 Android Studio 和 Gradle 插件,默认行为是同时生成 v1 + v2 + v3 签名。你打开 build.gradle 看看:

android {
    signingConfigs {
        release {
            // 签名文件配置...
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            // 默认使用 v1 + v2 + v3
        }
    }
}

手动关闭某些签名方案?老实说,我建议别动。除非你有非常特殊的理由,比如目标设备是 Android 6.0 以下的老旧机顶盒。

避坑提醒:如果你只签 v2,分发给 Android 6.0 以下的设备会直接安装失败。因为那些设备根本不认识 APK Signing Block。解决方案?要么保持 v1 + v2,要么放弃那批设备。我个人倾向于前者。

验证签名方案的命令

常用的工具是 apksigner,它在 Android SDK 的 build-tools 目录下。用法很简单:

# 查看签名信息
apksigner verify --print-certs app-release.apk

# 检查APK包含了哪些签名方案
apksigner verify --verbose app-release.apk

输出示例:

Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): true
Verified using v3 scheme (APK Signature Scheme v3): true
Verified using v4 scheme (APK Signature Scheme v4): false
Number of signers: 1

这个输出一目了然:哪些签名方案生效了,哪些没生效。

小技巧:如果你拿到的 APK 报“签名无效”,先跑这条命令看看。很多时候是 v1/v2 缺失,或者签名证书不对。我见过不少开发者在 CI/CD 流水线里配错了签名文件,导致发版后安装失败。

一些容易忽略的细节

  • v1 的兼容性问题:如果 APK 里包含 META-INF/*.SF 但缺少了某些文件的哈希条目,系统不会直接报错,而是跳过验证。这是个大隐患。
  • v2 的区块大小:APK Signing Block 有大小限制(目前是 1MB),但一般情况都够用。如果不够,检查签名证书链是否过长。
  • v3 的密钥轮转:虽然好用,但一旦轮转就不可逆。上一把私钥必须妥善保管,因为系统验证时会一直用到历史证书。
  • v4 的 .idsig 文件:不要丢了它。如果增量安装需要验证,但 .idsig 文件不存在,系统会回退到完整验证,增量更新的优势就没了。

总结一下

选签名方案,其实是个“兼容性 vs 安全性”的权衡。我的建议很明确:

  • 如果你只发布到 Google Play:让 Gradle 默认配置走,不用操心。
  • 如果你做企业分发或海外市场:至少 v1 + v2,推荐 v1 + v2 + v3。
  • 如果你做增量更新:加上 v4,记得同时保留 .idsig 文件。

嗯,Android 签名这个话题就说这么多。你不用一次性记住所有细节,但只要理解了每一代在解决什么问题,工作中遇到签名相关的问题时,就不会手忙脚乱。