@[TOC](文章目錄)
## 概述
APK 簽名是 Android 安全體系的核心支柱之一,它確保了應用的**身份真實性**、**內容完整性**和**更新可信性**。隨著 Android 系統(tǒng)的發(fā)展,簽名機制從傳統(tǒng)的 JAR 簽名(v1)演進到更安全高效的 APK Signature Scheme v2 及更高版本。
本文深入解析 **v1 與 v2 簽名機制的工作原理、結構差異、安全局限與優(yōu)勢**,并提供完整的簽名流程、驗證方法與生產環(huán)境最佳實踐。
## 1. APK 簽名的核心目的
APK 簽名不僅是發(fā)布應用的必要步驟,更是 Android 生態(tài)安全信任鏈的基礎。其主要目標包括:
| 目標 | 說明 |
|------|------|
|? **身份認證** | 驗證 APK 來自特定開發(fā)者,防止冒名發(fā)布 |
|? **完整性保護** | 確保 APK 在分發(fā)過程中未被篡改或注入惡意代碼 |
|? **權限共享** | 允許相同簽名的應用通過 `sharedUserId` 共享數據和組件 |
|? **安全更新** | 系統(tǒng)只允許使用相同簽名的 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)? # 開發(fā)者證書 + 對 CERT.SF 的簽名
├── AndroidManifest.xml
├── classes.dex
├── resources.arsc
└── res/, assets/, lib/, etc.
```
> ?? 文件名 `CERT` 會根據密鑰庫別名變化,如 `MYKEY.RSA`
### 2.3 簽名生成流程
#### 步驟 1:生成 `MANIFEST.MF`
對 APK 中**每一個非簽名文件**計算哈希值:
```bash
# 偽代碼
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` 內容進行簽名,并可選擇性地為每個條目添加額外摘要:
```bash
# 計算 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`
使用開發(fā)者的私鑰對 `CERT.SF` 進行數字簽名,并嵌入公鑰證書鏈:
```bash
# 使用 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 簽名的驗證過程
1. 使用 `CERT.RSA` 中的公鑰驗證 `CERT.SF` 的簽名
2. 計算當前 `MANIFEST.MF` 的哈希,與 `CERT.SF` 中的 `SHA1-Digest-Manifest` 比較
3. 對 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 簽名塊] ← 新增區(qū)域
? ? ↓
[ZIP 中央目錄]
? ? ↓
[ZIP 文件結尾 (EOCD)]
```
>? v2 簽名塊插入在 ZIP 數據與中央目錄之間,不影響 ZIP 解析。
### 3.3 簽名塊結構
簽名塊是一個包含多個 **ID-Value 對** 的二進制結構:
```
[塊大小] (8字節(jié))
[ID-Value 對列表]
[塊大小] (重復,用于快速定位)
```
關鍵 ID:
- `0x7109871a`:APK 簽名方案 v2 的簽名數據
- `0xf05368c0`:v3 簽名支持
- `0x21426444`:APK 簽名方案 v3 輪換元數據
### 3.4 v2 簽名生成流程
```java
// 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()); // 開發(fā)者證書鏈
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 簽名驗證流程
1. 定位并讀取 APK 簽名塊
2. 提取 `SignedData` 和簽名
3. 使用證書中的公鑰驗證簽名有效性
4. 重新計算 APK 內容的分塊哈希,與 `SignedData` 中的摘要比對
### 3.6 v2 簽名的核心優(yōu)勢
| 優(yōu)勢 | 說明 |
|------|------|
|? **完整保護** | 保護 ZIP 數據、中央目錄、文件名、壓縮方式等所有元數據 |
|? **高性能驗證** | 僅需一次簽名驗證 + 分塊哈希比對,速度提升 3-5 倍 |
|? **防篡改** | 任何字節(jié)修改都會導致哈希不匹配 |
|? **前向兼容** | 支持未來擴展(如 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 支持密鑰輪換,允許開發(fā)者安全更換簽名密鑰。?
>? **v4 簽名**:用于支持 Google Play 的 App Bundle 動態(tài)分發(fā)。
---
## 5. 實際簽名流程
### 5.1 使用 Android Studio(Gradle)
```gradle
android {
? ? signingConfigs {
? ? ? ? release {
? ? ? ? ? ? storeFile file("my-release-key.jks")
? ? ? ? ? ? storePassword "your_store_password"
? ? ? ? ? ? keyAlias "my-key-alias"
? ? ? ? ? ? keyPassword "your_key_password"
? ? ? ? ? ? // 推薦:同時啟用 v1 和 v2
? ? ? ? ? ? v1SigningEnabled true
? ? ? ? ? ? v2SigningEnabled true
? ? ? ? ? ? v3SigningEnabled true? // Android 9+
? ? ? ? }
? ? }
? ? buildTypes {
? ? ? ? release {
? ? ? ? ? ? signingConfig signingConfigs.release
? ? ? ? ? ? minifyEnabled true
? ? ? ? ? ? proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
? ? ? ? }
? ? }
}
```
### 5.2 使用 `apksigner` 命令行工具
```bash
# 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` 查看證書
```bash
# 查看 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` 深度驗證
```bash
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)或離線存儲 |
| **區(qū)分調試與發(fā)布密鑰** | 避免使用調試密鑰發(fā)布應用 |
| **啟用 v3 簽名(如需輪換)** | 支持未來密鑰輪換 |
| **定期審計簽名配置** | 防止配置泄露或錯誤 |
### ? 避免的錯誤
-? 使用默認的 `debug.keystore`
-? 將密鑰文件和密碼提交到 Git
-? 使用弱密碼(如 `android`)
-? 多人共享同一發(fā)布密鑰
-? 忽略簽名驗證警告
###? 密鑰安全管理
- 使用 `PKCS#12` 格式(`.p12`)替代 JKS
- 設置強密碼(12+ 位,含大小寫、數字、符號)
- 啟用 Google Play App Signing,由 Google 托管上傳密鑰
---
## 8. 總結
| 簽名方案 | 適用場景 | 推薦度 |
|---------|----------|--------|
| **v1 簽名** | 僅需兼容舊設備(< Android 7.0) |? 不推薦單獨使用 |
| **v2 簽名** | 所有現代應用的標準 |? **強烈推薦啟用** |
| **v1 + v2** | 兼顧兼容性與安全性 |? **生產環(huán)境最佳選擇** |
| **v1 + v2 + v3** | 需要密鑰輪換能力 |? 高級安全需求 |
>? **結論**:?
> **v2 簽名是現代 Android 應用的安全基石**。開發(fā)者應默認啟用 v1 和 v2 簽名,優(yōu)先使用強加密算法,并嚴格管理簽名密鑰。隨著 Android 生態(tài)的發(fā)展,v2 及以上簽名方案將成為唯一可信的發(fā)布標準。
通過理解簽名機制的底層原理,開發(fā)者不僅能正確配置發(fā)布流程,更能深入把握 Android 安全架構的設計哲學,為構建可信應用打下堅實基礎。