2. Android签名机制
签名这玩意儿,说白了就是给 APK 打个“防伪标签”。你想想看,Google Play 商店里那么多 App,凭什么相信你下载的微信是真微信?靠的就是签名。
但 Android 的签名机制不是一步到位的。从最早期的 JAR 签名,到后来的 v1、v2、v3、v4,每一代都在解决上一代遗留的问题。我在跟一些开发者聊天时发现,很多人只知道“哦,要用签名”,但对这几代的区别一脸懵。
没关系,我今天一次性给你讲清楚。
签名的本质是什么?
先问一个最简单的问题:签名到底在保护什么?
- 完整性:APK 里的文件有没有被篡改?
- 身份验证:这个 APK 到底是谁发布的?
- 不可否认性:开发者不能说自己没发过这个包。
这三条,是签名机制的核心目标。后面的每一代方案,都是围绕这三个点在做改进。
我记得有一次在做合规检测时,客户问我:“我只要签名了不就行了吗?为什么还分 v1、v2?”嗯,你听我慢慢说。
JAR 签名:最原始的方案
在 Android 还没出生之前,Java 世界就已经有了 JAR 签名方案。Android 最早期的签名机制,直接沿用了这套方案。
JAR 签名的核心思路很简单:
- 计算每个文件的哈希值。
- 把哈希值放进 META-INF/MANIFEST.MF 文件里。
- 再对 MANIFEST.MF 做一次哈希+签名,生成 .SF 和 .RSA 文件。
- 验证时,逐个文件检查哈希。
这套机制有两个致命问题,我在项目里踩过坑:
- 验证粒度太粗:它只验证文件内容,不验证文件结构。
- 容易被绕过:攻击者可以解开 APK,修改或删除 META-INF 里的签名文件,再重新打包。只要签名文件被删掉,系统就认为没签名,反而忽略了篡改。
APK 签名方案 v1:JAR 签名的 Android 化
v1 本质上就是 JAR 签名。但它做了一些 Android 特定的适配。
它的工作流程是这样的:
- 打包后,对所有未签名文件计算 SHA1 或 SHA256 哈希。
- 写入 META-INF/MANIFEST.MF。
- 对 MANIFEST.MF 签名,生成 META-INF/CERT.SF 和 CERT.RSA。
- 安装时,解析这些文件,逐文件验证。
v1 的问题在于:它只能保护 ZIP 条目内部的每个文件,但 ZIP 条目本身的顺序、额外字段、中央目录,这些它都不管。
APK 签名方案 v2:真正的突破
v2 是 Android 7.0(API 24)引入的。它解决了一个核心矛盾:签名应该保护整个 APK,而不是其中的每个文件。
v2 的设计思路完全不同:
- 签名信息被放在 APK 文件的一个特殊区块中(APK Signing Block)。
- 这个区块插在 ZIP 中央目录之前。
- 验证时,对整个 APK 的 “字节范围” 做签名。
换句话说:v2 签名保护的是 APK 文件本身,而不是 文件内容的集合。
我们来看一下 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)。系统在验证时,会沿着这条链往上查,确认这个新签名者有合法的继承权。
APK 签名方案 v4:增量更新的孪生兄弟
v4 是 Android 11(API 30)新增的方案。它专为 ADB 增量更新(Incremental Installation) 服务。
v4 签名的特点:
- 它不是一个完整的签名方案,而是 v2/v3 的补充。
- v4 签名以独立文件存在(.idsig)。
- 它包含 Merkle树根哈希,允许系统只验证文件的一部分。
增量更新允许用户只下载 APK 中的部分内容就开始安装。v4 签名的存在,就是为了让这种“边下边装”的场景依然能保证安全性。
SVG 图:四代签名方案演进对比
四代签名方案对比表
| 维度 | 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 以下的老旧机顶盒。
验证签名方案的命令
常用的工具是 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
这个输出一目了然:哪些签名方案生效了,哪些没生效。
一些容易忽略的细节
- 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 签名这个话题就说这么多。你不用一次性记住所有细节,但只要理解了每一代在解决什么问题,工作中遇到签名相关的问题时,就不会手忙脚乱。