shawshuai
shawshuai
发布于 2025-01-09 / 29 阅读
0
0

Android 应用签名密钥轮替

本文记录了项目中应客户安全团队要求,在 APK 更新过程中更改其签名密钥以满足更高的加密算法要求的过程。


2024年,项目被客户集团安全团队指摘,认为应用的签名不够安全,要求应用使用APK签名 v2~v4 版本并禁用 v1 版本。这项内容已经各项目中非常常见的指摘了,配置方式也很简单,在 build.gradle 文件中指定 v1 disable 即可。

不同的是,这次安全团队还要求“证书使用 SHA256 签名算法”。经过确认,现有应用证书使用的签名算法是 SHA1withRSA,也就是如果要满足安全团队这次的要求,需要使用新的证书对应用签名。

生成新的证书

可以通过 Android Studio 菜单中 Build > Generate Signed Bundle/APK 中选择生成新的密钥和密钥库。但在本项目实施时,发现此方法生成证书不能指定使用的加密算法,生成出来的证书依然是 SHA1withRSA。

于是改为通过一下命令生成新的证书:

keytool -genkeypair \
    -v -keystore <key_store_name>.jks \
    -keyalg RSA \
    -keysize 2048 \ 
    -validity 10000 \ 
    -alias <key_alias> \ 
    -storetype JKS \
    -sigalg SHA256withRSA \
    -keypass <replace_with_your_key_password> \
    -storepass <replace_with_your_keystore_password>

通过以上的命令,可以生成如下配置项的新密钥/密钥库:

  • 创建或使用 `<key_store_name>.jks` 密钥库文件。JKS(Java KeyStore)是 Java 默认的密钥库类型

  • 密钥对加密算法 `RSA`

  • 密钥长度 `2048` 位

  • 有效期 `10000` 天

  • 密钥对名称(唯一标识符)为 `<key_alias>`

  • 密钥库类型为 `JKS`

  • 签名证书算法 `SHA256withRSA`

  • 密钥密码 `<replace_with_your_key_password>`

  • 密钥库密码 `<replace_with_your_keystore_password>`

然后根据提示输入对应的信息:

您的名字与姓氏是什么?
  [Unknown]:  SomeName
您的组织单位名称是什么?
  [Unknown]:  OrganizationUnitName
您的组织名称是什么?
  [Unknown]:  OranizationName
您所在的城市或区域名称是什么?
  [Unknown]:  Chengdu
您所在的省/市/自治区名称是什么?
  [Unknown]:  Sichuan
该单位的双字母国家/地区代码是什么?
  [Unknown]:  CN
CN=SomeName, OU=OrganizationUnitName, O=OrganizationName, L=Chengdu, ST=Sichuan, C=CN是否正确?
  [否]:  y

正在为以下对象生成 2,048 位RSA密钥对和自签名证书 (SHA256withRSA) (有效期为 10,000 天):
	CN=SomeName, OU=OrganizationUnitName, O=OrganizationName, L=Chengdu, ST=Sichuan, C=CN

[正在存储<key_store_name>.jks]

Warning:
JKS 密钥库使用专用格式。建议使用 "keytool -importkeystore -srckeystore <key_store_name>.jks -destkeystore <key_store_name>.jks -deststoretype pkcs12" 迁移到行业标准格式 PKCS12。

然后,通过以下命令轮替密钥并生成轮替关系文件:

apksigner rotate \
    --out <path_to_lineage_file> \
    --old-signer --ks <old_signer_jks_name>.jks \
    --new-signer --ks <new_signer_jks_name>.jks 

Keystore password for old signer: <input your password here>

Keystore password for new signer: <input your password here>

Key "<new key alias will display here>" password for new signer:<input your password here>

执行完成之后,会生成一个名称为 `<path_to_lineage_file>` 的文件证明新旧证书的轮替关系。

生成轮替关系之后,现在版本的 build.gradle 配置文件中目前暂时不能直接配置并应用签名。需要通过 apksigner 手动对于打包完成的 apk 文件进行签名。

配置为 Gradle 脚本:

commandLine "apksigner", "sign", "--in", outputFileName, "--out", "${name}_signed.apk",  
"--ks", "$buildDir/../../<old_jks_file_name>.jks", "--ks-pass", "pass:<old_keystore_password>",  
"--next-signer", "--ks", "$buildDir/../../<new_jks_file_name>.jks", "--ks-pass", "pass:<new_keystore_password>",  
"--lineage", "$buildDir/../../lineage",  
"--rotation-min-sdk-version", "26",  
"--v1-signing-enabled", "false",  
"--v2-signing-enabled", "true",  
"--v3-signing-enabled", "true",  
"--v4-signing-enabled", "true"

以上命令中 `--rotation-min-sdk-version` 是指定轮替开始的版本,文档表明应该指定为 `28` 及以后的版本,但通过实际测试表明指定为 `26` 也不会报错,但实际的密钥轮替依然从版本 `28` 开始。

执行以上的命令对 apk 文件进行签名后,apk 将同时包含两个证书,并在指定版本的 Android 设备上开始轮替到新的密钥。

发布相关

如果应用通过 APK 二进制文件直接分发到用户,那么通过以上步骤完成了密钥轮替机制就已经完成了证书的升级工作了。但是如果应用涉及到了在 Google Play 的发布(无论是只在 Google Play 进行应用的分发还是 Google Play 和其它渠道同步分发),那么发布的工作流程还有一点不同。

使用 APK 发布

如果使用 APK 发布应用,使用了上面描述的“密钥轮替”机制后,尝试上传新版本到 Google Play。出现以下的错误提示:

即:Google Play 使用 APK 发布的应用,如果使用密钥轮替机制,则应用的 `minSdk` 需要在 30 及以上。但这个要求在当下时间段对于绝大部分应用来说可能不能覆盖足够广的用户群,可能会有潜在的用户流失,所以本项目决定改为采用 bundle 在 Google Play 进行发布。

使用 aab (Android App Bundle) 发布

如果应用已经启用了 Google Play 签名服务,那么启用之后提交就已经是使用 App Bundle 了。可以在本地通过 bundletool 执行一边打包为 apk 的命令,以检查可能发生的潜在错误。

在本项目中,AndroidManifest.xml 文件中定义了两个重复的 meta-data 值;在本地使用 Android Studio 和 bundletool 工具均可以成功完成打包,且并无报错;但尝试将更新上传到 Google Play 时提示错误。所以即使本地签名、打包流程均无问题出现,也可能会在更新上传到 Play 之后遇到问题。

其它相关的操作

对于部分 SDK,在注册使用的时候需要填写发布版本证书的部分信息。用于身份验证和计费。

以高德地图 SDK 为例,需要填写发布版本安全码 SHA1 进行身份验证。

在使用密钥轮替的情况下, 需要在 SDK 管理后台新建一个应用并填写新发布安全码 SHA1。使用旧证书的应用也要在 SDK 的管理后台保留,直到应用完全废弃旧的签名证书。

保留旧证书版本的时间

当所有用户都升级到了进行了密钥轮替的版本之后(即只使用旧证书签名的应用版本已被废弃),如果只考虑 APK 分发,就可以不用旧证书签名了。后续的版本需要同时包含新旧证书 或 只包含新证书才能完成应用的升级。


评论