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 或版本控制。推荐方案:

  1. 环境变量 + gradle.properties:在 CI 平台或本地 shell 导出变量,Gradle 读取。
  2. 专用加密文件:使用 gradle-encrypt-plugin 将 keystore 密码加密存储于 keystore-password.gpg。
  3. 系统密钥链(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)中,密钥安全是最薄弱环节。推荐采用 临时解密 + 即时签名 + 立即擦除 的流水线模式。

自动签名流水线安全流程 CI 触发构建 解密加密 Keystore 加载凭据(环境变量/HSM) Gradle assembleRelease apksigner 验证 + 哈希校验 擦除临时密钥文件 说明:密钥仅在工作内存中存活,构建完成后立即从文件系统和环境变量中删除 建议使用云 KMS(Google Cloud KMS / AWS KMS)远程签名,本地从不接触私钥

4.4.1 CI/CD 密钥安全实践

  1. 使用存储库加密变量:GitHub Actions 的 Secrets、GitLab CI 的 Variables、Jenkins 的 Credentials Binding。
  2. 签名任务只在 protected branch(如 main)上触发,避免 PR branch 使用发布密钥。
  3. 签名后立即执行 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 门禁,防止无效签名包上架。