签名原理#
APK 签名使用非对称加密技术确保应用的身份验证和完整性:
| 目的 | 说明 |
|---|---|
| 身份验证 | 确认应用来源于特定开发者 |
| 完整性保护 | 确保 APK 内容未被篡改 |
| 安全更新 | 只有相同密钥签名的应用才能覆盖安装 |
| 应用沙箱 | 签名证书定义应用的用户 ID |
核心概念#
- 私钥 (Private Key): 开发者持有,用于生成签名
- 公钥 (Public Key): 嵌入 APK 证书中,用于验证签名
- Keystore: 存储密钥对的加密容器文件(
.jks或.keystore)
签名流程#
| 步骤 | 工具 | 说明 |
|---|---|---|
| 1. 生成密钥库 | keytool | 创建包含密钥对的 keystore 文件 |
| 2. 构建未签名 APK | Gradle/IDE | 编译生成未签名的 APK |
| 3. ZipAlign 对齐 | zipalign | 优化 APK 文件结构 |
| 4. 签名 APK | apksigner/jarsigner | 应用数字签名 |
| 5. 验证签名 | apksigner verify | 确认签名正确 |
签名验证过程#
Android 安装 APK 时的验证步骤:
- 哈希计算: 计算 APK 内容的密码学哈希值
- 公钥提取: 从 APK 中提取开发者的公钥证书
- 签名解密: 使用公钥解密数字签名,获取原始哈希
- 哈希比对: 比较计算的哈希与解密的哈希
- 结果判定: 哈希匹配则验证通过,否则拒绝安装
签名版本对比#
版本演进#
| 版本 | 引入时间 | 最低 Android 版本 | 主要特性 |
|---|---|---|---|
| V1 | 初始版本 | 所有版本 | 基于 JAR 签名 |
| V2 | Android 7.0 | Nougat (API 24) | 全文件签名 |
| V3 | Android 9.0 | Pie (API 28) | 支持密钥轮换 |
| V4 | Android 11 | R (API 30) | 增量安装支持 |
V1 签名(JAR Signing)#
基于 JAR 签名标准,对 APK 内的每个文件单独签名。
签名内容位置:
APK
├── META-INF/
│ ├── MANIFEST.MF # 文件清单及哈希
│ ├── CERT.SF # 签名文件
│ └── CERT.RSA/DSA # 证书和签名
└── ... (其他文件)plaintext- ✅ 兼容所有 Android 版本
- ❌ 不保护 ZIP 元数据
- ❌ 验证速度慢
V2 签名(APK Signature Scheme v2)#
将整个 APK 作为一个整体进行签名。
签名块位置:
┌─────────────────────────┐
│ Contents of ZIP │ ← 文件内容
├─────────────────────────┤
│ APK Signing Block │ ← V2/V3 签名块
├─────────────────────────┤
│ Central Directory │ ← ZIP 中央目录
├─────────────────────────┤
│ End of Central Directory│
└─────────────────────────┘plaintext- ✅ 验证速度更快
- ✅ 保护完整 APK(包括 ZIP 元数据)
- ✅ 支持多签名者
V3 签名(APK Signature Scheme v3)#
在 V2 基础上增加密钥轮换支持。
核心特性: 签名血统 (Signature Lineage)
- 包含历史签名证书链
- 每个祖先证书为其后继证书背书
- 允许在不破坏更新的情况下更换签名密钥
适用场景: 签名密钥泄露、团队人员变动、组织架构调整
⚠️ V3 不支持多签名者,密钥轮换建议 Android 13+ 使用
V4 签名(APK Signature Scheme v4)#
基于 Merkle 哈希树的流式签名方案。
- 签名存储在独立文件:
<apk-name>.apk.idsig - 专为增量安装设计
- 必须与 V2 或 V3 配合使用
版本特性对比#
| 特性 | V1 | V2 | V3 | V4 |
|---|---|---|---|---|
| 全文件保护 | ❌ | ✅ | ✅ | ✅ |
| 验证速度 | 慢 | 快 | 快 | 最快 |
| 密钥轮换 | ❌ | ❌ | ✅ | - |
| 多签名者 | ❌ | ✅ | ❌ | - |
| 增量安装 | ❌ | ❌ | ❌ | ✅ |
签名工具#
| 工具 | 来源 | 用途 | 支持版本 |
|---|---|---|---|
keytool | JDK | 生成/管理密钥库 | - |
jarsigner | JDK | JAR/APK 签名 | V1 |
apksigner | Android SDK | APK 签名 | V1/V2/V3/V4 |
zipalign | Android SDK | APK 对齐优化 | - |
apksigner vs jarsigner#
| 方面 | apksigner | jarsigner |
|---|---|---|
| 推荐度 | ✅ 官方推荐 | ⚠️ 仅限 V1 |
| 签名版本 | V1/V2/V3/V4 | 仅 V1 |
| 对齐处理 | 可在签名后对齐 | 必须先对齐再签名 |
常用命令#
生成 Keystore#
keytool -genkeypair \
-v \
-keystore my-release-key.jks \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias my-key-alias \
-storepass <store-password> \
-keypass <key-password>bash使用 apksigner 签名(推荐)#
# 基本签名
apksigner sign \
--ks my-release-key.jks \
--ks-key-alias my-key-alias \
--out app-signed.apk \
app-unsigned.apk
# 指定签名版本
apksigner sign \
--ks my-release-key.jks \
--ks-key-alias my-key-alias \
--v1-signing-enabled true \
--v2-signing-enabled true \
--v3-signing-enabled true \
--v4-signing-enabled true \
--out app-signed.apk \
app-unsigned.apkbash使用 jarsigner 签名(V1)#
# 先对齐
zipalign -v 4 app-unsigned.apk app-aligned.apk
# 再签名
jarsigner \
-verbose \
-sigalg SHA256withRSA \
-digestalg SHA-256 \
-keystore my-release-key.jks \
app-aligned.apk \
my-key-aliasbash⚠️ 使用
jarsigner时必须先 zipalign,后签名
验证签名#
# 使用 apksigner 验证
apksigner verify --verbose --print-certs app-signed.apk
# 查看签名版本
apksigner verify -v app.apk | grep -E "v[1-4]"bashJAR 包调用#
直接调用 apksigner.jar#
# 签名
java -jar $ANDROID_SDK/build-tools/<version>/lib/apksigner.jar sign \
--ks my-release-key.jks \
--ks-key-alias my-key-alias \
--out app-signed.apk \
app-unsigned.apk
# 验证
java -jar $ANDROID_SDK/build-tools/<version>/lib/apksigner.jar verify \
--verbose \
--print-certs \
app-signed.apkbashJava 编程调用#
import com.android.apksig.ApkSigner;
import java.io.File;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
public class SignAPK {
public static void signApk(File inputApk, File outputApk,
File keystoreFile, String alias,
String storePass, String keyPass) throws Exception {
// 加载密钥库
KeyStore keyStore = KeyStore.getInstance("JKS");
try (FileInputStream fis = new FileInputStream(keystoreFile)) {
keyStore.load(fis, storePass.toCharArray());
}
// 获取私钥和证书
PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, keyPass.toCharArray());
X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
// 创建签名配置
ApkSigner.SignerConfig signerConfig = new ApkSigner.SignerConfig.Builder(
"CERT", privateKey, List.of(cert)
).build();
// 执行签名
ApkSigner apkSigner = new ApkSigner.Builder(List.of(signerConfig))
.setInputApk(inputApk)
.setOutputApk(outputApk)
.setV1SigningEnabled(true)
.setV2SigningEnabled(true)
.setV3SigningEnabled(true)
.build();
apkSigner.sign();
}
}javaGradle 依赖:
dependencies {
implementation 'com.android.tools.build:apksig:8.2.0'
}groovy常见问题#
为什么需要同时使用多个签名版本?#
确保兼容性。旧设备只支持 V1,新设备优先使用 V2+。同时签名可覆盖所有设备。
使用 jarsigner 还是 apksigner?#
优先使用 apksigner。它支持更多签名版本,且是 Google 官方推荐工具。
签名密钥丢失怎么办?#
Android 9+ 可使用 V3 签名的密钥轮换功能。对于旧密钥,需联系 Google Play 支持。