Android 证书从 0 到长期可维护:Keystore、签名与密钥轮换

从 keystore、alias 和证书指纹的关系开始,完成发布密钥创建、Gradle 配置、APK/AAB 验签、Play App Signing、CI 注入与灾难恢复。

Android 发布密钥不是一个普通构建文件。应用上线后,签名身份直接关系到后续版本能否覆盖安装。本文使用通用示例讲清创建、配置、验证和备份流程;真实密码、密钥文件和账号信息不得写入仓库或文章。

先分清四个概念

Android 签名配置中最常见的几个词容易混在一起:

  • Keystore:保存一个或多个密钥条目的容器文件。
  • Alias:Keystore 内某个密钥条目的名称。
  • 私钥:用于给 APK 或 App Bundle 签名,必须严格保密。
  • 证书:包含公钥和主体信息,可用于校验签名身份和计算指纹。

同一个 Keystore 可以包含多个 Alias,但正式项目通常让每个产品使用清晰、可追踪的发布密钥。不要因为技术上能共用,就让所有应用共用一个无法独立轮换的密钥。

先决定签名托管方式

自主管理应用签名密钥

团队自己保存最终 App Signing Key。优势是完全控制,风险是密钥丢失或泄露时恢复困难。使用不同应用商店或企业分发时,通常仍需要团队保管长期发布密钥。

Google Play App Signing

Google Play 托管最终应用签名密钥,团队日常上传使用 Upload Key:

  1. 本地或 CI 使用 Upload Key 给 AAB 签名。
  2. Google Play 验证上传身份。
  3. Play 使用 App Signing Key 为用户分发的 APK 签名。

启用后要分别记录 App Signing Key Certificate 和 Upload Key Certificate 的 SHA-1、SHA-256 指纹。第三方登录、地图、支付等平台可能要求填写其中一种,不能混用。

第一步:生成发布 Keystore

使用 JDK 自带的 keytool:

keytool -genkeypair -v \
  -keystore product-release.jks \
  -alias product-release \
  -keyalg RSA \
  -keysize 2048 \
  -validity 10000

命令会交互式要求输入 Keystore 密码、密钥密码和证书主体。主体信息应使用团队认可的通用资料,不要填写临时昵称。

生成后立即检查:

keytool -list -v \
  -keystore product-release.jks \
  -alias product-release

保存以下信息:

  • Keystore 文件名与受控存储位置。
  • Store Password。
  • Key Alias。
  • Key Password。
  • SHA-1 与 SHA-256 指纹。
  • 创建日期、有效期和负责人。

密码与 Keystore 文件必须分开保存。不要把两者一起放在共享网盘目录中。

第二步:配置 Gradle Release 签名

签名参数不应直接写进 build.gradle。本地可以从不提交到 Git 的 keystore.properties 读取,CI 则优先使用加密环境变量或凭据文件。

Groovy DSL 示例:

android {
  signingConfigs {
    release {
      storeFile file(System.getenv("ANDROID_KEYSTORE_PATH"))
      storePassword System.getenv("ANDROID_KEYSTORE_PASSWORD")
      keyAlias System.getenv("ANDROID_KEY_ALIAS")
      keyPassword System.getenv("ANDROID_KEY_PASSWORD")
    }
  }

  buildTypes {
    release {
      signingConfig signingConfigs.release
      minifyEnabled true
    }
  }
}

Kotlin DSL 示例:

android {
  signingConfigs {
    create("release") {
      storeFile = file(System.getenv("ANDROID_KEYSTORE_PATH"))
      storePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD")
      keyAlias = System.getenv("ANDROID_KEY_ALIAS")
      keyPassword = System.getenv("ANDROID_KEY_PASSWORD")
    }
  }

  buildTypes {
    getByName("release") {
      signingConfig = signingConfigs.getByName("release")
      isMinifyEnabled = true
    }
  }
}

配置完成后,不要只看 Gradle 的 BUILD SUCCESSFUL。必须对最终产物验签。

第三步:构建 APK 与 AAB

常用发布命令:

./gradlew clean assembleRelease
./gradlew bundleRelease

APK 适合直接安装或部分渠道分发,AAB 通常用于 Google Play。不同渠道对包格式和签名方案要求可能不同,发布前应核对目标商店当前规则。

如果项目存在 productFlavor,要确认构建变体使用的是正式 applicationId、版本号、服务配置和签名配置,避免把测试环境包提交到线上应用。

第四步:验证 APK 签名

使用 Android SDK Build Tools 中的 apksigner:

apksigner verify --verbose --print-certs app-release.apk

输出应至少检查:

  • 验签结果为成功。
  • Signer 数量符合预期。
  • SHA-256 指纹与登记记录一致。
  • v1、v2、v3 或 v4 签名方案符合目标系统与渠道要求。

查看 APK 的包名和版本:

aapt dump badging app-release.apk | head

签名正确但包名、versionCode 或渠道配置错误,同样不能作为可发布产物。

第五步:验证 AAB 与签名身份

AAB 本身是上传产物,最终安装 APK 通常由应用商店生成。可以先查看 JAR 签名信息:

jarsigner -verify -verbose -certs app-release.aab

使用 Google Play App Signing 时,还应在 Play Console 的应用完整性页面记录:

  • App Signing Key Certificate 指纹。
  • Upload Key Certificate 指纹。
  • 当前密钥升级或轮换状态。

对接 Firebase、Google Sign-In、地图或其他开放平台时,先确认对方要求的是开发证书、Upload Key 还是最终 App Signing Key,避免指纹填对了格式却填错了证书。

CI 中安全注入 Keystore

CI 的基本原则是:密钥只在任务运行期间出现,日志中不可见,任务结束后删除。

一种做法是将 Keystore 作为 CI 的加密文件保存,运行时写入临时目录:

export ANDROID_KEYSTORE_PATH="$RUNNER_TEMP/product-release.jks"

test -f "$ANDROID_KEYSTORE_PATH"
keytool -list \
  -keystore "$ANDROID_KEYSTORE_PATH" \
  -alias "$ANDROID_KEY_ALIAS" \
  -storepass "$ANDROID_KEYSTORE_PASSWORD" > /dev/null

./gradlew bundleRelease

构建日志不要启用会回显环境变量的调试模式。任务结束后清理临时 Keystore、生成的属性文件和解码中间文件。

密钥备份与灾难恢复

至少准备两份加密备份,并放在不同故障域:

  1. 团队密码库或受控密钥管理系统。
  2. 离线加密介质或独立的安全备份位置。

备份恢复演练比“已经备份”更重要。应定期在隔离环境验证:

  • Keystore 文件可以读取。
  • 密码和 Alias 正确。
  • 可以签出测试 APK。
  • 签名指纹与线上版本一致。

如果只有某位成员的电脑能构建 Release,这不是流程,而是单点故障。

Upload Key 丢失怎么办

启用 Google Play App Signing 后,如果丢失的是 Upload Key,可以按 Play Console 当前流程申请重置 Upload Key。重新生成上传密钥后,提交新的证书供平台审核。

如果未启用托管签名,并且最终应用签名私钥彻底丢失,后果会严重得多:旧用户通常无法直接安装由新密钥签名的更新。处理能力取决于目标商店、Android 版本和是否提前启用了可用的密钥升级机制。

因此,是否启用 Play App Signing 和如何备份自管密钥,应在首次正式发布前决定,而不是丢失后再补救。

常见错误怎么排查

Keystore was tampered with, or password was incorrect

可能是 Store Password 错误,也可能拿错 Keystore。先核对文件哈希和受控记录,不要连续试错导致凭据暴露在日志中。

Alias 不存在

运行 keytool -list -keystore product-release.jks 查看实际 Alias。文件名正确不代表内部条目名称正确。

签名后无法覆盖安装

对比新旧 APK 的证书指纹、applicationId 和版本号。覆盖安装要求包名一致、签名身份兼容,并且 versionCode 满足升级条件。

第三方登录提示签名不匹配

分别检查 Debug、Release、Upload Key 和 App Signing Key 的 SHA-1/SHA-256。线上商店安装包通常不是 Debug 证书,也不一定是 Upload Key。

CI 本地成功、线上失败

检查 CI 是否拿到了正确 Keystore、Alias 和 Release 变体;再使用 apksigner --print-certs 对 CI 产物做最终验证,而不是只比对配置文件。

密钥轮换策略

密钥轮换必须先确认分发平台和最低 Android 版本的支持范围。安全的基本流程是:

  1. 记录当前线上签名指纹和分发平台状态。
  2. 按平台能力申请密钥升级或 Upload Key 重置。
  3. 在测试轨道验证升级安装、登录、推送和第三方 SDK。
  4. 更新所有依赖证书指纹的开放平台配置。
  5. 保留旧密钥的受控归档,不立即销毁。

不要把“重新生成一个 Keystore”当作轮换。新密钥能否被现有安装接受,取决于完整的签名升级链路。

最终检查清单

  • Keystore、Alias 和密码已记录并分开保管。
  • SHA-1、SHA-256 指纹已登记。
  • Release 配置没有硬编码密码。
  • APK 已通过 apksigner verify。
  • AAB 已通过 jarsigner 基础校验。
  • 包名、versionCode、versionName 和环境配置正确。
  • 已区分 Upload Key 与 App Signing Key。
  • CI 日志不会输出密钥或密码。
  • 已完成至少一次备份恢复演练。

Android 证书管理的底线是:任何一次正式构建都能确认“使用了哪把密钥”,任何密钥事故都能明确“如何恢复或轮换”。做到这两点,签名才真正进入可维护状态。