如何在 CI/CD 流水线中安全地存储和使用签名密钥?

解读

面试官问的不是“怎么签名”,而是“怎么在自动化流程里既能让构建服务器拿到密钥,又不让密钥泄露”。国内场景下,代码仓库往往放在 GitLab-EE 或私有 Gitea,构建节点既跑在阿里云/腾讯云 ECS,也跑在本地机房 KVM;既要对接 fir.im、蒲公英、华为 AppGallery,又要兼容 Google Play AAB。密钥一旦泄漏,不仅攻击者可以伪造升级包,还可能被监管机构认定为“未履行个人信息保护义务”,带来合规风险。因此,回答必须兼顾“技术可行性 + 国内合规 + 成本可控”。

知识点

  1. 密钥形态:上传密钥(upload key)、发布密钥(release key)、v1/v2/v3/v4 签名方案、PEPK 工具、Google Play App Signing 的“分离密钥”模型。
  2. 国内合规:GB/T 35273 个人信息安全规范要求“密钥不应明文存储在代码仓库”,等保 2.0 要求“密钥托管在密码产品或硬件模块中”。
  3. 机密管理方案:
    • 云原生:阿里云 KMS、腾讯云 KMS、华为云 KMS、AWS KMS(外企)。
    • 私有:HashiCorp Vault 开源版、GitLab CI/CD Variables(文件类型)、Jenkins Credentials + Mask Passwords。
  4. 构建工具链:Gradle Android Plugin 7.x 支持 signingConfigs {} 从环境变量读取密钥库密码、keyAlias、keyPassword;R8/ProGuard 规则需排除签名相关日志。
  5. 最小权限原则:CI Runner 仅对“构建阶段”开放 KMS 解密权限,打包完成后立即清理临时文件;使用 tmpfs 挂载 /tmp,防止密钥落盘。
  6. 审计与轮换:CI 侧记录“哪次 Job 使用了哪个密钥版本”,通过 KMS 自动轮换或手动生成新密钥并上传 PEPK 加密包到 Google Play;国内渠道需同步更新公钥指纹到华为、OPPO、vivo 后台。
  7. 双轨签名:海外渠道走 Google Play App Signing,国内渠道走“上传密钥”自行管理,降低单点泄漏风险。

答案

国内落地可按“三层防护”实施:

  1. 密钥生成与拆分:在离线加密机或国密合规的 USBKey 内生成 release.jks,导出加密分片(KMS 加密后的 base64)后删除本地明文;上传密钥与发布密钥分离,上传密钥仅用于 CI,发布密钥由法人保管。
  2. 机密注入:
    • GitLab CI:在“设置-CI/CD-变量”里创建文件类型变量 RELEASE_KEYSTORE,内容填 KMS 加密后的 base64;再创建普通变量 KEYSTORE_PWD、KEY_ALIAS、KEY_PWD,全部开启“隐藏”与“受保护分支”标志。
    • Runner 侧:使用阿里云 KMS 的“凭据管家”角色,绑定 RAM 策略仅允许 decrypt 指定密钥 ID;构建脚本先用 aliyun-kms-cli 解密拿到临时 keystore,写入内存盘 /dev/shm/keystore.jks,再执行 ./gradlew assembleRelease。
  3. 构建与销毁:Gradle 配置 signingConfigs.release 只读环境变量,构建完成后立即 rm -f /dev/shm/keystore.jks,并通过 find /build -name "*.jks" -delete 二次清扫;CI 日志开启“掩码”功能,防止密码被打印。
  4. 合规审计:在 KMS 侧开启“密钥使用日志”投递到日志服务 SLS,保存 180 天;每次发版 Merge Request 必须附带“密钥版本号 + 审计日志截图”,由安全团队 review。
  5. 轮换机制:每 90 天触发一次“上传密钥”轮换,使用 Google Play PEPK 工具生成新加密包,同时在华为、小米后台更新指纹;旧版本密钥立即吊销,CI 变量同步更新。

一句话总结:把密钥托管在“云 KMS + 内存盘 + 最小权限 RAM 角色”里,构建时临时解密、用完即毁,配合审计与轮换,就能在 CI/CD 中既安全又合规地使用签名密钥。

拓展思考

  1. 如果公司完全离线、不能上云,如何用 Vault + PKCS11 硬件密码机在本地 Jenkins 实现同等安全等级?
  2. 当团队扩大到 200 人、每日 500 次构建,如何基于 Kubernetes CSI 驱动把 KMS 解密挂载为只读 Volume,避免每个 Runner 都调用一次 decrypt 产生费用热点?
  3. 国内厂商要求“渠道包”使用不同 RSA 密钥,如何设计一条流水线同时输出 30 个渠道包,而只解密一次主密钥,其余使用派生密钥?
  4. 未来 AOSP 可能强制要求 APK Signature Scheme v4 中的“密钥轮换证明”,CI 侧如何提前集成 Android Keystore Key Attestation 验证,防止上传已被轮转的废密钥?