文章目录
- 概述
- 1. APK 签名的核心目的
- 2. v1 签名(JAR 签名)详解
- 2.1 基本原理
- 2.2 签名文件结构
- 2.3 签名生成流程
- 步骤 1:生成 `MANIFEST.MF`
- 步骤 2:生成 `CERT.SF`
- 步骤 3:生成 `CERT.RSA`
- 2.4 v1 签名的验证过程
- 2.5 v1 签名的局限性
- 3. v2 签名(APK Signature Scheme v2)详解
- 3.1 设计理念
- 3.2 APK 文件结构(v2)
- 3.3 签名块结构
- 3.4 v2 签名生成流程
- 3.5 v2 签名验证流程
- 3.6 v2 签名的核心优势
- 4. v1 与 v2 签名对比
- 5. 实际签名流程
- 5.1 使用 Android Studio(Gradle)
- 5.2 使用 `apksigner` 命令行工具
- 6. 验证签名信息
- 6.1 使用 `keytool` 查看证书
- 6.2 使用 `apksigner` 深度验证
- 7. 最佳实践与安全建议
- 推荐做法
- ❌ 避免的错误
- 密钥安全管理
- 8. 总结
概述
APK 签名是 Android 安全体系的核心支柱之一,它确保了应用的身份真实性、内容完整性和更新可信性。随着 Android 系统的发展,签名机制从传统的 JAR 签名(v1)演进到更安全高效的 APK Signature Scheme v2 及更高版本。
本文深入解析 v1 与 v2 签名机制的工作原理、结构差异、安全局限与优势,并提供完整的签名流程、验证方法与生产环境最佳实践。
1. APK 签名的核心目的
APK 签名不仅是发布应用的必要步骤,更是 Android 生态安全信任链的基础。其主要目标包括:
| 目标 | 说明 |
|---|---|
| 身份认证 | 验证 APK 来自特定开发者,防止冒名发布 |
| 完整性保护 | 确保 APK 在分发过程中未被篡改或注入恶意代码 |
| 权限共享 | 允许相同签名的应用通过 sharedUserId 共享数据和组件 |
| 安全更新 | 系统只允许使用相同签名的 APK 进行版本升级,防止恶意覆盖 |
| Google Play 验证 | Play 商店依赖签名信息进行应用归属验证与更新追踪 |
2. v1 签名(JAR 签名)详解
2.1 基本原理
v1 签名基于 Java 的 JAR 签名标准(RFC 1319),在 ZIP 文件内部 对每个文件进行独立签名,不改变 ZIP 结构。
2.2 签名文件结构
APK 文件结构:
├── META-INF/
│ ├── MANIFEST.MF # 所有文件的哈希清单
│ ├── CERT.SF # MANIFEST.MF 的签名摘要
│ └── CERT.RSA (或 .DSA) # 开发者证书 + 对 CERT.SF 的签名
├── AndroidManifest.xml
├── classes.dex
├── resources.arsc
└── res/, assets/, lib/, etc.
文件名
CERT会根据密钥库别名变化,如MYKEY.RSA
2.3 签名生成流程
步骤 1:生成 MANIFEST.MF
对 APK 中每一个非签名文件计算哈希值:
# 伪代码
for file in apk_entries:
if file not in META-INF/:
hash = Base64.encode(sha1(file_content))
MANIFEST.MF += "Name: $file_path\nSHA1-Digest: $hash\n\n"
步骤 2:生成 CERT.SF
对 MANIFEST.MF 内容进行签名,并可选择性地为每个条目添加额外摘要:
# 计算 MANIFEST.MF 的哈希
manifest_hash = Base64.encode(sha1(MANIFEST.MF))
# 构建 CERT.SF
CERT.SF = "SHA1-Digest-Manifest: $manifest_hash\n\n"
for entry in MANIFEST.MF.entries:
entry_hash = Base64.encode(sha1(entry))
CERT.SF += "Name: $entry_name\nSHA1-Digest: $entry_hash\n\n"
步骤 3:生成 CERT.RSA
使用开发者的私钥对 CERT.SF 进行数字签名,并嵌入公钥证书链:
# 使用 PKCS#7 标准封装
CERT.RSA = PKCS7_Signature {
content: CERT.SF,
signature: RSA_Sign(sha1(CERT.SF), private_key),
certificates: [developer_cert, CA_certs...]
}
2.4 v1 签名的验证过程
- 使用
CERT.RSA中的公钥验证CERT.SF的签名 - 计算当前
MANIFEST.MF的哈希,与CERT.SF中的SHA1-Digest-Manifest比较 - 对 APK 中每个文件重新计算哈希,与
MANIFEST.MF中的记录比对
2.5 v1 签名的局限性
| 问题 | 描述 |
|---|---|
| ⚠️ 不保护 ZIP 元数据 | 攻击者可修改 ZIP 中央目录或添加“幻影数据”而不破坏签名 |
| ⚠️ 性能差 | 需逐个文件验证哈希,I/O 开销大 |
| ⚠️ 易受 ZIP 重排序攻击 | ZIP 条目顺序变化不影响签名,但可能影响某些解析器 |
| ⚠️ 哈希算法弱 | 默认使用 SHA-1,存在碰撞风险 |
3. v2 签名(APK Signature Scheme v2)详解
为解决 v1 的安全缺陷,Google 在 Android 7.0(API 24) 引入 v2 签名方案。
3.1 设计理念
v2 签名采用“全文件哈希 + 签名块”模式,将 APK 视为一个整体进行保护。
3.2 APK 文件结构(v2)
[ZIP 条目数据块]↓
[APK 签名块] ← 新增区域↓
[ZIP 中央目录]↓
[ZIP 文件结尾 (EOCD)]
v2 签名块插入在 ZIP 数据与中央目录之间,不影响 ZIP 解析。
3.3 签名块结构
签名块是一个包含多个 ID-Value 对 的二进制结构:
[块大小] (8字节)
[ID-Value 对列表]
[块大小] (重复,用于快速定位)
关键 ID:
0x7109871a:APK 签名方案 v2 的签名数据0xf05368c0:v3 签名支持0x21426444:APK 签名方案 v3 轮换元数据
3.4 v2 签名生成流程
// 1. 分块哈希(防哈希洪水攻击)
byte[] apkContent = readAllBytes(apkFile);
List<byte[]> chunks = split(apkContent, 1MB);List<Digest> contentDigests = new ArrayList<>();for (byte[] chunk : chunks) {contentDigests.add(new Digest("SHA-256", sha256(chunk)));}// 2. 构建签名数据(SignedData)SignedData signedData = new SignedData();signedData.setDigests(contentDigests);signedData.setCertificates(loadCertificates()); // 开发者证书链signedData.setMinSignatureSchemeId(2); // 最低签名方案signedData.addOptionalAttribute("target_sdk", 34);// 3. 使用私钥签名byte[] signatureBytes = sign(signedData.toByteArray(), privateKey, "SHA256withRSA");// 4. 构建签名块ApkSigningBlock block = new ApkSigningBlock();block.addIdValue(0x7109871a, serialize(signedData, signatureBytes, publicKey));// 5. 写入 APK(插入在 ZIP 数据后、EOCD 前)writeSigningBlock(apkOutputStream, block);
3.5 v2 签名验证流程
- 定位并读取 APK 签名块
- 提取
SignedData和签名 - 使用证书中的公钥验证签名有效性
- 重新计算 APK 内容的分块哈希,与
SignedData中的摘要比对
3.6 v2 签名的核心优势
| 优势 | 说明 |
|---|---|
| 完整保护 | 保护 ZIP 数据、中央目录、文件名、压缩方式等所有元数据 |
| 高性能验证 | 仅需一次签名验证 + 分块哈希比对,速度提升 3-5 倍 |
| 防篡改 | 任何字节修改都会导致哈希不匹配 |
| 前向兼容 | 支持未来扩展(如 v3、v4) |
| 抗重排序攻击 | ZIP 条目顺序变化会被检测到 |
4. v1 与 v2 签名对比
| 特性 | v1 签名 | v2 签名 |
|---|---|---|
| 引入版本 | Android 1.0 | Android 7.0 (API 24) |
| 签名位置 | ZIP 内部(META-INF/) | ZIP 外部(APK 签名块) |
| 保护范围 | 单个文件内容 | 整个 APK(含元数据) |
| 验证速度 | 慢(O(n) 文件数) | 快(O(1) + 分块哈希) |
| 安全性 | 中等(SHA-1,不保护元数据) | 高(SHA-256,全保护) |
| 兼容性 | 所有 Android 版本 | Android 7.0+ |
| 签名算法 | SHA1withRSA, SHA1withDSA | SHA256withRSA, SHA256withECDSA |
| 是否可被篡改 | 是(ZIP 末尾添加数据) | 否 |
v3 签名:Android 9 支持密钥轮换,允许开发者安全更换签名密钥。
v4 签名:用于支持 Google Play 的 App Bundle 动态分发。
5. 实际签名流程
5.1 使用 Android Studio(Gradle)
android {signingConfigs {release {storeFile file("my-release-key.jks")storePassword "your_store_password"keyAlias "my-key-alias"keyPassword "your_key_password"// 推荐:同时启用 v1 和 v2v1SigningEnabled truev2SigningEnabled truev3SigningEnabled true // Android 9+}}buildTypes {release {signingConfig signingConfigs.releaseminifyEnabled trueproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}
}
5.2 使用 apksigner 命令行工具
# 1. 生成密钥库(首次)
keytool -genkey -v -keystore my-upload-key.jks \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-dname "CN=Your Name, O=Your Org, L=City, ST=State, C=CN" \
-alias upload-key
# 2. 签名 APK
apksigner sign \
--key-pass pass:your_key_password \
--ks-pass pass:your_store_password \
--ks my-release-key.jks \
--ks-key-alias my-key-alias \
--v1-signing-enabled true \
--v2-signing-enabled true \
--v3-signing-enabled true \
app-release-unsigned.apk
# 3. 验证签名
apksigner verify --verbose app-release-unsigned.apk
6. 验证签名信息
6.1 使用 keytool 查看证书
# 查看 APK 的签名证书
keytool -printcert -jarfile app-release.apk
# 输出示例:
# Owner: CN=Your Name, OU=Your Org, O=Your Company
# Issuer: CN=Your Name, OU=Your Org, O=Your Company
# Serial number: 5f8e2d1c
# Valid from: Tue Oct 14 00:00:00 CST 2025 until: Fri Jan 22 00:00:00 CST 2053
# Certificate fingerprints:
# SHA1: AA:BB:CC:DD:EE:FF:11:22:33:44:55:66:77:88:99:00:11:22:33:44
# SHA256: 1A:2B:3C:4D:5E:6F:7A:8B:9C:0D:1E:2F:3A:4B:5C:6D:7E:8F:9A:0B:1C:2D:3E:4F:5A:6B:7C:8D:9E:0F:1A:2B
6.2 使用 apksigner 深度验证
apksigner verify --verbose --print-certs app-release.apk
# 输出包含:
# Signer #1 certificate SHA-256 digest: ...
# Digest using Digest SHA-256: ...
# Signature algorithm: SHA256withRSA
# Signed using apksigner: true
# Signer #1:
# Digest: SHA-256
# Signature: SHA256withRSA
# Signed: true
7. 最佳实践与安全建议
推荐做法
| 实践 | 说明 |
|---|---|
| 同时启用 v1 和 v2 | 确保兼容 Android 7.0 以下设备 |
| 使用强密钥算法 | RSA 2048+ 或 ECDSA 256+ |
| 安全存储密钥 | 使用硬件安全模块(HSM)或离线存储 |
| 区分调试与发布密钥 | 避免使用调试密钥发布应用 |
| 启用 v3 签名(如需轮换) | 支持未来密钥轮换 |
| 定期审计签名配置 | 防止配置泄露或错误 |
❌ 避免的错误
- 使用默认的
debug.keystore - 将密钥文件和密码提交到 Git
- 使用弱密码(如
android) - 多人共享同一发布密钥
- 忽略签名验证警告
密钥安全管理
- 使用
PKCS#12格式(.p12)替代 JKS - 设置强密码(12+ 位,含大小写、数字、符号)
- 启用 Google Play App Signing,由 Google 托管上传密钥
8. 总结
| 签名方案 | 适用场景 | 推荐度 |
|---|---|---|
| v1 签名 | 仅需兼容旧设备(< Android 7.0) | 不推荐单独使用 |
| v2 签名 | 所有现代应用的标准 | 强烈推荐启用 |
| v1 + v2 | 兼顾兼容性与安全性 | 生产环境最佳选择 |
| v1 + v2 + v3 | 需要密钥轮换能力 | 高级安全需求 |
结论:
v2 签名是现代 Android 应用的安全基石。开发者应默认启用 v1 和 v2 签名,优先使用强加密算法,并严格管理签名密钥。随着 Android 生态的发展,v2 及以上签名方案将成为唯一可信的发布标准。
通过理解签名机制的底层原理,开发者不仅能正确配置发布流程,更能深入把握 Android 安全架构的设计哲学,为构建可信应用打下坚实基础。