4 签名与密钥管理
签名证书是 Android 应用身份与完整性的基石。密钥泄露会导致恶意应用冒充正版、静默更新、窃取敏感权限。本章从 keystore 加密存储、签名配置安全、APK 签名方案选择 和 自动签名流水线安全 四个维度,给出可落地的高安全性实践。
安全基线:2023 年起 Google Play 强制要求新应用使用 APK Signature Scheme v2 或更高版本。生产环境密钥必须使用硬件安全模块(HSM)或云 KMS 签名,本地仅使用加密存储的副本。
4.1 Keystore 加密存储
传统 .jks(Java KeyStore)基于专有格式,密码强度弱且容易被暴力破解。推荐使用 PKCS#12(.p12 或 .pfx)格式,它采用标准加密算法(默认使用 AES‑256‑CBC 保护私钥)。
4.1.1 生成并加密 Keystore
# 生成 PKCS12 格式的 keystore(推荐)
keytool -genkey -v -keystore release.p12 -storetype pkcs12 \
-alias mykey -keyalg RSA -keysize 2048 \
-validity 10000 -storepass <store-pass> -keypass <key-pass>
# 将现有 JKS 转换为 PKCS12
keytool -importkeystore -srckeystore legacy.jks \
-destkeystore secured.p12 -deststoretype pkcs12
4.1.2 在 Gradle 中安全引用密钥凭据
禁止 将明文密码写入 build.gradle 或版本控制。推荐方案:
- 环境变量 + gradle.properties:在 CI 平台或本地 shell 导出变量,Gradle 读取。
- 专用加密文件:使用
gradle-encrypt-plugin将 keystore 密码加密存储于keystore-password.gpg。 - 系统密钥链(macOS/Windows):通过命令行工具注入。
// gradle.properties(仅示例,切勿提交含真实密码的版本)
systemProp.keystorePassword=${env.KEYSTORE_PASSWORD}
systemProp.keyPassword=${env.KEY_PASSWORD}
// 或从本地加密文件读取
def keystorePw = new File("${rootDir}/secret/keystore-pw.gpg").text.trim()
4.2 签名配置安全
签名配置必须遵循 最小权限 和 环境隔离 原则:debug 与 release 使用不同密钥,且 release 密钥仅在生产构建或 CI/CD 受信节点中解密。
android {
signingConfigs {
release {
storeFile file("../secure/keystore/release.p12")
storePassword System.getenv("RELEASE_STORE_PASS")
keyAlias System.getenv("RELEASE_KEY_ALIAS")
keyPassword System.getenv("RELEASE_KEY_PASS")
}
debug {
storeFile file("../secure/keystore/debug.keystore")
storePassword "android"
keyAlias "androiddebugkey"
keyPassword "android"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
危险模式:不要将
signingConfigs.debug 用于发布构建;不要在 build.gradle 中直接硬编码任何密码;不要在构建日志中输出密码(通过 logger.debug 或 println 泄露)。
4.3 APK 签名方案选择
| 方案 | 签名位置 | 安全强度 | 兼容性 | 建议 |
|---|---|---|---|---|
| v1 (JAR) | META-INF/*.SF、*.RSA | 低(可被篡改并重新签名) | Android 1.0+ | 仅用于兼容老旧 SDK |
| v2 (APK Sig Block) | APK 签名块 | 高(保护整个 APK 字节) | Android 7.0+ | 必需 对新应用启用 |
| v3 (v2 + 密钥轮换) | APK 签名块 + 最小支持权证 | 极高(支持密钥升级/降级) | Android 9.0+ | 长期维护应用推荐 |
| v4 (增量签名) | APK 签名块 + 独立 .idsig 文件 | 极高(支持增量更新验证) | Android 11+(Google Play 内部) | 仅在 Google Play 需要时启用 |
// 在 build.gradle 中启用 v2 和 v3
android {
defaultConfig {
signingConfig signingConfigs.release
v2SigningEnabled true
v3SigningEnabled true
}
}
// 验证 APK 签名方案
apksigner verify --verbose app-release.apk
4.4 自动签名流水线安全
在 CI/CD(Jenkins、GitHub Actions、GitLab CI)中,密钥安全是最薄弱环节。推荐采用 临时解密 + 即时签名 + 立即擦除 的流水线模式。
4.4.1 CI/CD 密钥安全实践
- 使用存储库加密变量:GitHub Actions 的 Secrets、GitLab CI 的 Variables、Jenkins 的 Credentials Binding。
- 签名任务只在 protected branch(如 main)上触发,避免 PR branch 使用发布密钥。
- 签名后立即执行
apksigner verify并校验 v2/v3 签名块完整性。
# GitHub Actions 示例(片段)
jobs:
sign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Decrypt keystore
run: |
gpg --quiet --batch --yes --decrypt \
--passphrase="${{ secrets.KEYSTORE_GPG_PASS }}" \
-o secure/release.p12 secure/release.p12.gpg
- name: Build & Sign
run: ./gradlew assembleRelease
env:
RELEASE_STORE_PASS: ${{ secrets.RELEASE_STORE_PASS }}
RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
RELEASE_KEY_PASS: ${{ secrets.RELEASE_KEY_PASS }}
- name: Verify
run: apksigner verify --verbose app/build/outputs/apk/release/app-release.apk
- name: Secure Cleanup
run: rm -f secure/release.p12
性能与稳定性优化:
- 使用
apksigner sign --ks-key-alias替代 Gradle 签名以支持并行构建,避免 JVM 锁冲突。 - 在 CI pipeline 中为签名任务分配独立缓存层(如 Docker layer caching),减少 keystore 解密重复开销。
- 对大体积 APK(>200MB)启用
v4SigningEnabled true配合增量发布,减少重签名时间。
4.5 常见错误与调试
| 错误场景 | 根因 | 解决方案 |
|---|---|---|
jarsigner: java.security.KeyStoreException: KeyStore not initialized |
密码为空或 keystore 路径错误 | 检查环境变量是否注入;使用路径并确认文件可读 |
APK Signature Block not found (v2) |
使用了过旧的 Gradle 插件或未开启 v2 | 升级 AGP ≥ 3.0;在 defaultConfig 中设置 v2SigningEnabled true |
| CI 构建后 OS 仍残留解密文件 | 未执行擦除步骤或构建异常导致中断 | 使用 always() 块保证清理;启用临时文件系统(RAM disk) |
AndroidX warning: Missing classes detected while running apksigner |
分 dex 配置与签名方案不兼容 | 升级到 AGP 8.x 并启用 R8 + v3 签名 |
部署前务必运行完整性验证:apksigner verify --print-certs app-release.apk 检查证书指纹是否与 Play Console 一致。将 verity 步骤加入 CI 门禁,防止无效签名包上架。