android 签名安全

1.使用 GitLab CI/CD 的 Secret 管理机制 + 动态生成签名文件

1.1 将签名信息存储在 GitLab CI/CD 的变量中(Secret Variables)

在 GitLab 项目或 Group 的 Settings → CI/CD → Variables 中添加以下内容:
KEYSTORE_BASE64:将 .keystore 文件转成 base64 字符串后存储
KEY_ALIAS
KEY_PASSWORD
STORE_PASSWORD
⚠️:这些变量需要设置为 “Protected” 且 “Masked”,确保不会被打印在日志中。

你可以使用命令行工具将 .keystore 文件转换成 base64 字符串,非常简单。以下是在 Windows、macOS、Linux 下的操作方法。

macOS / Linux / Git Bash 处理方式:

base64 -w 0 release.keystore > keystore.base64.txt

-w 0 表示 不要换行
有些系统用的是 --wrap=0 或 -b 0:

base64 --wrap=0 release.keystore > keystore.base64.txt

1.2 在 CI 中恢复 keystore 文件

在 .gitlab-ci.yml 的构建步骤中,先将 keystore 文件从 base64 解码出来:

  - echo $KEYSTORE_BASE64 | base64 -d > release.keystore

或(macOS/Linux runner 有时需要加 -n 处理换行):

  - echo -n "$KEYSTORE_BASE64" | base64 -d > release.keystore

然后在 gradle.properties 或 build.gradle 中引用这些环境变量。

android {
    signingConfigs {
        release {
            storeFile file("${rootDir}/release.keystore")
            storePassword System.getenv("STORE_PASSWORD")
            keyAlias System.getenv("KEY_ALIAS")
            keyPassword System.getenv("KEY_PASSWORD")
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

避免将 release.keystore 保留到后续阶段中,打完包后立刻清除:

after_script:
  - rm -f release.keystore

1.3 如果项目根目录已经有 release.keystore 文件,就跳过 base64 解码;否则再生成:

script:
  - |
    if [ ! -f "release.keystore" ]; then
      echo "release.keystore not found, generating from base64..."
      echo -n "$KEYSTORE_BASE64" | base64 -d > release.keystore
    else
      echo "release.keystore already exists, skip generation."
    fi
  - ./gradlew assembleRelease

2 ✅ 正确防止 keystore 被打包进 APK 的方法

1. 不要把 release.keystore 放在 app 模块的 src/ 子目录或 assets/ 目录中。

建议放在项目根目录或 ci_keys/ 目录下,绝不放在如下路径:

app/src/main/assets/

app/src/main/res/

app/libs/

否则会被误打入 APK。

2. 配置 Gradle 排除非必要文件(保险起见)

在 app/build.gradle 中添加打包排除逻辑:

android {
    packagingOptions {
        exclude "release.keystore"
        exclude "**/release.keystore"
    }
}

或者更强硬一点:

android.applicationVariants.all { variant ->
    variant.outputs.each { output ->
        def outputDir = output.outputFile.parentFile
        def keystoreFile = file("${project.rootDir}/release.keystore")
        if (keystoreFile.exists()) {
            println "Deleting keystore before packaging: ${keystoreFile}"
            keystoreFile.delete()
        }
    }
}

这个做法是在打包之前删除 keystore,可防止漏掉清理操作。

3. CI 中删除 keystore 的时机要合理

在打包完成后,立即删除:

script:
  - |
    if [ ! -f "release.keystore" ]; then
      echo -n "$KEYSTORE_BASE64" | base64 -d > release.keystore
    fi
  - ./gradlew assembleRelease
  - rm -f release.keystore   # ✅ 及时删除

问题 1:将 keystore 放在根目录是否一定不会被打包进 APK??

✅ 答案:确定。

前提是你没有人为操作或配置将根目录的文件 copy 进 APK 的资源目录(比如 assets/ 或 res/)。

原因:
Gradle 打包 APK 时,默认只会打包以下内容到 APK 中:

文件夹路径 是否打包进 APK 内容说明
src/main/res/ ✅ 是 编译成 resources.arsc
src/main/assets/ ✅ 是 打包为原始文件到 APK
libs/ ✅ 是 (.jar) 打进 classes.dex 或 classpath
src/main/java/ ✅ 是 编译为 class 并打包进 dex
项目根目录、build/ ❌ 否 不会自动打进 APK

🔒 所以只要你没主动将根目录的文件拷贝到打包路径(比如 assets/),它就绝对不会被打包进 APK 中。

3.推荐做法

3.1 keystore 位置

将 keystore 放置在项目根目录,或者通过 gitlab CI 变量配置下发,然后重新生成keystore文件。
从安全性来说是放在 gitlab 中最好。但是,将keystore放置在项目根目录更方便。看公司对于签名信息的安全的重视程度。

3.2 签名信息

1.将签名信息配置在local.properties中:

RELEASE_STORE_PASSWORD=你的keystore密码
RELEASE_KEY_ALIAS=你的别名
KEYSTORE_PASSWORD=你的key密码

2.在 build.gradle.kts 中读取 local.properties 的方法
local.properties 不是 Gradle 默认加载的配置文件,所以你需要手动加载它:

import java.util.Properties
import java.io.FileInputStream

// 定义一个变量读取 local.properties
val localProperties = Properties().apply {
    val localPropertiesFile = rootProject.file("local.properties")
    if (localPropertiesFile.exists()) {
        load(FileInputStream(localPropertiesFile))
    }
}

android {
    signingConfigs {
        getByName("debug") {
            storeFile = file("${rootDir}/xxx.jks")
            storePassword = localProperties.getProperty("RELEASE_STORE_PASSWORD") ?: System.getenv("KEYSTORE_PASSWORD")
            keyAlias = localProperties.getProperty("RELEASE_KEY_ALIAS")?: System.getenv("KEYSTORE_ALIAS")
            keyPassword = localProperties.getProperty("KEYSTORE_PASSWORD") ?: System.getenv("KEYSTORE_PASSWORD")
        }
        create("release") {
            storeFile = file("${rootDir}/xxx.jks")
            storePassword = localProperties.getProperty("RELEASE_STORE_PASSWORD") ?: System.getenv("KEYSTORE_PASSWORD")
            keyAlias = localProperties.getProperty("RELEASE_KEY_ALIAS")?: System.getenv("KEYSTORE_ALIAS")
            keyPassword = localProperties.getProperty("KEYSTORE_PASSWORD") ?: System.getenv("KEYSTORE_PASSWORD")
        }
    }

    buildTypes {
        getByName("debug") {
            signingConfig = signingConfigs.getByName("debug")
        }

        getByName("release") {
            signingConfig = signingConfigs.getByName("release")
        }
    }
}

3. 注意事项

local.properties 不要提交到代码仓库,在 .gitignore 中确保忽略它。

如果文件不存在或缺少字段,以上代码也不会报错,会得到 null,你可以做 null 判断或设置默认值。

CI/CD 里不要用 local.properties,应使用环境变量或 CI 的 Secret Variables。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文韬_武略

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值