不能在build.gradle中硬编码storePassword,因其会被提交至Git导致密钥泄露,攻击者可重签名APK发布恶意更新;最通用方案是使用.gitignore保护的keystore.properties文件抽离配置,并在CI中通过环境变量兜底。

为什么不能在build.gradle里直接写storePassword
因为 build.gradle 文件通常会被提交到 Git,一旦泄露,攻击者就能用你的 storeFile 和 storePassword 重签名 APK,发布恶意更新。Google 明确要求签名密钥保护级别等同于银行密码——硬编码等于把钥匙挂在门把手上。
keystore.properties 是最通用的安全方案
核心思路是把密码、别名、路径抽离成独立文件,并确保它不进版本控制。操作要点如下:
-
keystore.properties必须放在项目根目录(与app/同级),但要立刻加进.gitignore:keystore.properties - 文件内容只写纯键值对,**不要加引号、不要空格、不要注释**:
storePassword=abcd1234 keyPassword=efgh5678 keyAlias=myreleasekey storeFile=../certs/myapp-release.jks
- 在
app/build.gradle开头加载它,加 try-catch 防止本地没配导致构建失败:def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file("keystore.properties") if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } else { println("⚠️ keystore.properties not found. Release build will fail.") } - 在
signingConfigs中引用时,用keystoreProperties['xxx'],不是keystoreProperties.xxx(后者会空指针)
gradle.properties 适合团队共享的非敏感配置
如果你有多个环境共用的密钥片段(比如 API key 前缀)、或想统一管理 debug/release 的 BuildConfig 字段,gradle.properties 更合适。它默认不被 Git 跟踪,且能被所有模块读取。
例如在 gradle.properties 里写:
API_KEY_PART1=abc BUILD_CONFIG_ENV=prod
,然后在 build.gradle 里:
buildConfigField "String", "API_KEY", "\"${API_KEY_PART1}xyz\""
。注意:它**不能存完整密码**,因为 IDE 和 Gradle 日志可能意外打印其值。
CI/CD 环境下必须用环境变量兜底
本地开发用 keystore.properties 没问题,但 Jenkins/GitHub Actions 等 CI 环境不能传文件。此时必须改用环境变量,否则构建会失败:
- 在 CI 配置中安全注入变量:
STORE_PASSWORD、KEY_PASSWORD、KEY_ALIAS、STORE_FILE_PATH - 在
build.gradle加 fallback 逻辑:storePassword System.getenv("STORE_PASSWORD") ?: keystoreProperties['storePassword'] keyPassword System.getenv("KEY_PASSWORD") ?: keystoreProperties['keyPassword'] storeFile file(System.getenv("STORE_FILE_PATH") ?: keystoreProperties['storeFile']) - 务必确认 CI 平台对变量值做了掩码处理(如 GitHub Actions 的
secrets),否则日志里会明文暴露
最容易被忽略的是:storeFile 路径在 CI 上往往和本地不同,且不能用相对路径(比如 ../certs/),必须用绝对路径或从环境变量传入;另外,Windows 和 Linux 的路径分隔符差异也会导致 file() 解析失败。











