主页

返回

Android 应用签名

Android APK 签名原理、V1/V2/V3/V4 版本对比、签名工具使用及常用命令

签名原理#

APK 签名使用非对称加密技术确保应用的身份验证和完整性:

目的说明
身份验证确认应用来源于特定开发者
完整性保护确保 APK 内容未被篡改
安全更新只有相同密钥签名的应用才能覆盖安装
应用沙箱签名证书定义应用的用户 ID

核心概念#

  • 私钥 (Private Key): 开发者持有,用于生成签名
  • 公钥 (Public Key): 嵌入 APK 证书中,用于验证签名
  • Keystore: 存储密钥对的加密容器文件(.jks.keystore

签名流程#

步骤工具说明
1. 生成密钥库keytool创建包含密钥对的 keystore 文件
2. 构建未签名 APKGradle/IDE编译生成未签名的 APK
3. ZipAlign 对齐zipalign优化 APK 文件结构
4. 签名 APKapksigner/jarsigner应用数字签名
5. 验证签名apksigner verify确认签名正确

签名验证过程#

Android 安装 APK 时的验证步骤:

  1. 哈希计算: 计算 APK 内容的密码学哈希值
  2. 公钥提取: 从 APK 中提取开发者的公钥证书
  3. 签名解密: 使用公钥解密数字签名,获取原始哈希
  4. 哈希比对: 比较计算的哈希与解密的哈希
  5. 结果判定: 哈希匹配则验证通过,否则拒绝安装

签名版本对比#

版本演进#

版本引入时间最低 Android 版本主要特性
V1初始版本所有版本基于 JAR 签名
V2Android 7.0Nougat (API 24)全文件签名
V3Android 9.0Pie (API 28)支持密钥轮换
V4Android 11R (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 配合使用

版本特性对比#

特性V1V2V3V4
全文件保护
验证速度最快
密钥轮换-
多签名者-
增量安装

签名工具#

工具来源用途支持版本
keytoolJDK生成/管理密钥库-
jarsignerJDKJAR/APK 签名V1
apksignerAndroid SDKAPK 签名V1/V2/V3/V4
zipalignAndroid SDKAPK 对齐优化-

apksigner vs jarsigner#

方面apksignerjarsigner
推荐度✅ 官方推荐⚠️ 仅限 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.apk
bash

使用 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-alias
bash

⚠️ 使用 jarsigner 时必须先 zipalign,后签名

验证签名#

# 使用 apksigner 验证
apksigner verify --verbose --print-certs app-signed.apk

# 查看签名版本
apksigner verify -v app.apk | grep -E "v[1-4]"
bash

JAR 包调用#

直接调用 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.apk
bash

Java 编程调用#

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();
    }
}
java

Gradle 依赖:

dependencies {
    implementation 'com.android.tools.build:apksig:8.2.0'
}
groovy

常见问题#

为什么需要同时使用多个签名版本?#

确保兼容性。旧设备只支持 V1,新设备优先使用 V2+。同时签名可覆盖所有设备。

使用 jarsigner 还是 apksigner?#

优先使用 apksigner。它支持更多签名版本,且是 Google 官方推荐工具。

签名密钥丢失怎么办?#

Android 9+ 可使用 V3 签名的密钥轮换功能。对于旧密钥,需联系 Google Play 支持。


参考资料#