4. Keystore 密码和密钥密码管理
密码管理这件事,说大不大,说小不小。我见过不少项目,签名配置能写进版本控制里,密码明文躺在 git 历史中——嗯,我是说“见过”,而不是“做过”。密码泄露的风险往往不是攻防技术的问题,而是管理习惯的问题。
Keystore 里其实有两层密码:store 密码(Keystore 密码)和key 密码(密钥密码)。它们各自管什么,为什么需要分开,怎么安全地管理它们,下面挨个聊。
4.1 两层密码:Store 和 Key
很多新手以为 Keystore 只有一个密码。实际上,打开 .jks 文件用 store 密码,读取里面的某个密钥条目还需要 key 密码。你可以把它们想象成:
- Store 密码——仓库大门的锁
- Key 密码——保险柜里每个箱子的小锁
当用 keytool -list 查看 Keystore 内容时,只需要 store 密码。但如果你想导出证书或签名,那就要提供 key 密码。我个人习惯把这两个密码设置为不同的值,这样即使某个 key 密码意外泄露,攻击者也无法直接把整个 Keystore 掏走。
下面这张图能帮你快速理解两层密码的关系:
4.2 创建时就要区分好
用 keytool -genkeypair 时,默认会让 store 密码和 key 密码相同。命令行里有个 -keypass 参数可以单独指定。我曾经在一家创业公司接手项目,发现上一个同事把大量 debug 签名和 release 签名混在同一个 Keystore 里,所有密钥密码都是一样的。结果有一次误操作覆盖了 debug 密钥,导致整个团队无法安装测试包……
- 生产签名使用单独的 Keystore,不要和调试签名混在一起
- store 密码和 key 密码尽量不同
- 每个密钥条目的 key 密码也可以不同(尤其是多个应用共用一个 Keystore 时)
4.3 常见错误:密码写死在构建脚本里
很多 CI/CD 配置文件会把密码直接写进 signingConfigs,比如:
android {
signingConfigs {
release {
storeFile file("release.jks")
storePassword "123456"
keyAlias "mykey"
keyPassword "123456"
}
}
}
为什么我不建议这么干?
因为在 Git 仓库历史里,密码会被永久保留。即使你后来删掉并修改了密码,旧版本中的密码依然可以被任何人看到。说实话,这不是技术问题,是流程问题。
我个人的改进方案:把密码放到 local.properties 或者环境变量中读取:
android {
signingConfigs {
release {
storeFile file("release.jks")
storePassword System.getenv("STORE_PASSWORD") ?: project.findProperty("storePassword")
keyAlias System.getenv("KEY_ALIAS") ?: "release"
keyPassword System.getenv("KEY_PASSWORD") ?: project.findProperty("keyPassword")
}
}
}
这样密码就不会提交到版本控制系统,只有本地环境变量或者 gradle.properties(记得把它加到 .gitignore)里才会有。
4.4 密码丢失了怎么办?
这是个让人头疼的问题。Keystore 和密码是一对一绑定的,密码丢失后,没有任何后门可以找回。你想想看,如果 Google 能帮你找回,那黑客也能利用同样的漏洞。
应用商店里已经发布的旧版本将无法使用该签名更新。唯一办法是更换签名包名(或申请签名变更),但这会失去用户数据继承、推送 等。
我记得团队里有个同事,把 release 签名放在 U 盘里,结果 U 盘摔坏了。密码虽然还记得,但 Keystore 文件本身也损坏了——没有备份,只能重新申请签名。从那以后,我们制定了严格的密码和文件备份策略。
4.5 密码管理方案对比
我整理了几种常见的密码管理方式,供你参考:
| 方案 | 安全性 | 可维护性 | 适用场景 |
|---|---|---|---|
| 环境变量 | 中 | 高 | CI/CD、本地开发 |
| 密码管理器(1Password/LastPass) | 高 | 中 | 团队共享、生产环境 |
| 加密配置文件(如 encrypted-properties) | 高 | 低(需额外工具解密) | 需要脚本自动化部署的场景 |
| 硬件安全模块(HSM) | 极高 | 低 | 大型企业、金融 App |
4.6 密码复杂度要求
Android 官方其实没有强制规定 Keystore 密码的复杂度,但作为安全最佳实践,我建议你遵循下面的规则:
- 长度至少 12 位——最好包含大小写字母、数字和特殊字符
- 不要使用常见的词汇或日期——比如公司名、项目名、生日
- store 密码和 key 密码不要相同——前面解释过原因
- 定期更换密码——但记住:更换密码不等于更换密钥
你可以用
keytool -storepasswd 修改 store 密码,用 keytool -keypasswd 修改 key 密码。这不会改变私钥本身,已经发布的 APK 签名依然有效。但如果密钥丢失了,改密码也没有用。
4.7 避坑指南:记住这几个教训
- 我曾经把生产签名密码记在 Notion 里,结果 Notion 账号被恶意登录,虽然没造成实际损失,但想想都后怕。现在我用离线密码管理器 + 物理备份。
- 还有一个团队把 Keystore 文件放在共享文件夹里,密码写在代码注释中。嗯,他们的应用后来被抄袭者轻松重新签名并上传到第三方商店……
- 最离谱的一次——有人把 store 密码和 key 密码设成了同一个,还写在了 build.gradle 里提交到了公共仓库。我接手时发现密码是 "keystore"……
说句实在话,密码管理不是技术难题,而是习惯问题。你只需要在项目初始化时多花 5 分钟把流程建立好,就能避免后面很多麻烦。
在创建 Keystore 时,顺手生成一个包含密码和别名的凭据文件,加密后存储在本地的
~/.android/creds.enc 中。这样即使你忘了密码,也可以解密查看。但千万!不要把解密密钥放在同一台机器上。