在 build.gradle 中签名 APK 而不放入密钥存储信息

我试图设置签名过程,以便密钥库,密码和密钥密码 不是存储在项目的 build.gradle文件。

目前,我在 build.gradle中有以下内容:

android {
...
signingConfigs {
release {
storeFile file("my.keystore")
storePassword "store_password"
keyAlias "my_key_alias"
keyPassword "key_password"
}
}


buildTypes {
release {
signingConfig signingConfigs.release
}
}
}

它工作得非常好,但是我将 storePasswordkeyPassword的值放在了我的存储库中。我也不想把 storeFilekeyAlias放在那里。

有没有办法改变 build.gradle,使它可以从一些外部来源获得密码(如文件,只驻留在我的计算机上) ?

当然,更改后的 build.gradle应该可以在任何其他计算机上使用(即使计算机没有访问密码)。

我正在使用 Android Studio 和 Mac OS X Maverics,如果这很重要的话。

101660 次浏览

我就是这么做的,使用环境变量

  signingConfigs {
release {
storeFile file(System.getenv("KEYSTORE"))
storePassword System.getenv("KEYSTORE_PASSWORD")
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
}

Groovy 的优点是可以自由地混合 Java 代码,并且使用 java.util.Properties在键/值文件中读取非常容易。也许还有一种更简单的方法来使用惯用 Groovy,但 Java 仍然非常简单。

创建一个 keystore.properties文件(在这个例子中,在项目的根目录 settings.gradle旁边,不过您可以把它放在任何您喜欢的地方:

storePassword=...
keyPassword=...
keyAlias=...
storeFile=...

把这个加到你的 build.gradle:

allprojects {
afterEvaluate { project ->
def propsFile = rootProject.file('keystore.properties')
def configName = 'release'


if (propsFile.exists() && android.signingConfigs.hasProperty(configName)) {
def props = new Properties()
props.load(new FileInputStream(propsFile))
android.signingConfigs[configName].storeFile = file(props['storeFile'])
android.signingConfigs[configName].storePassword = props['storePassword']
android.signingConfigs[configName].keyAlias = props['keyAlias']
android.signingConfigs[configName].keyPassword = props['keyPassword']
}
}
}

阅读一些链接后:

Http://blog.macromates.com/2006/keychain-access-from-shell/ Http://www.thoughtworks.com/es/insights/blog/signing-open-source-android-apps-without-disclosing-passwords

由于您正在使用 MacOSX,因此可以使用 Keychain Access 来存储您的密码。

How to add password in Keychain Access

然后在你的分级脚本里:

/* Get password from Mac OSX Keychain */
def getPassword(String currentUser, String keyChain) {
def stdout = new ByteArrayOutputStream()
def stderr = new ByteArrayOutputStream()
exec {
commandLine 'security', '-q', 'find-generic-password', '-a', currentUser, '-gl', keyChain
standardOutput = stdout
errorOutput = stderr
ignoreExitValue true
}
//noinspection GroovyAssignabilityCheck
(stderr.toString().trim() =~ /password: '(.*)'/)[0][1]
}

使用方法如下:

GetPassword (currentUser,“ Android _ Store _ Password”)

/* Plugins */
apply plugin: 'com.android.application'


/* Variables */
ext.currentUser = System.getenv("USER")
ext.userHome = System.getProperty("user.home")
ext.keystorePath = 'KEY_STORE_PATH'


/* Signing Configs */
android {
signingConfigs {
release {
storeFile file(userHome + keystorePath + project.name)
storePassword getPassword(currentUser, "ANDROID_STORE_PASSWORD")
keyAlias 'jaredburrows'
keyPassword getPassword(currentUser, "ANDROID_KEY_PASSWORD")
}
}


buildTypes {
release {
signingConfig signingConfigs.release
}
}
}

或者,如果你想以一种更加类似于自动生成的 gradle 代码的方式应用 Scott Barta 的答案,你可以在你的项目根文件夹中创建一个 keystore.properties文件:

storePassword=my.keystore
keyPassword=key_password
keyAlias=my_key_alias
storeFile=store_file

并修改你的等级代码:

// Load keystore
def keystorePropertiesFile = rootProject.file("keystore.properties");
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))


...


android{


...


signingConfigs {
release {
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
}
}


...


}

您可以将此属性文件存储在模块的根目录中,在这种情况下只需省略 rootProject,还可以修改此代码,使其具有针对不同密钥存储库和密钥别名的多组属性。

您可以从命令行请求密码:

...


signingConfigs {
if (gradle.startParameter.taskNames.any {it.contains('Release') }) {
release {
storeFile file("your.keystore")
storePassword new String(System.console().readPassword("\n\$ Enter keystore password: "))
keyAlias "key-alias"
keyPassword new String(System.console().readPassword("\n\$ Enter keys password: "))
}
} else {
//Here be dragons: unreachable else-branch forces Gradle to create
//install...Release tasks.
release {
keyAlias 'dummy'
keyPassword 'dummy'
storeFile file('dummy')
storePassword 'dummy'
}
}
}


...


buildTypes {
release {


...


signingConfig signingConfigs.release
}


...
}


...

这个答案以前出现过: https://stackoverflow.com/a/33765572/3664487

最简单的方法是创建 ~/.gradle/gradle.properties文件。

ANDROID_STORE_PASSWORD=hunter2
ANDROID_KEY_PASSWORD=hunter2

然后你的 build.gradle文件可以是这样的:

android {
signingConfigs {
release {
storeFile file('yourfile.keystore')
storePassword ANDROID_STORE_PASSWORD
keyAlias 'youralias'
keyPassword ANDROID_KEY_PASSWORD
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}

接受的答案使用一个文件来控制使用哪个密钥存储库对驻留在项目的同一根文件夹中的 APK 进行签名。当我们像使用 饭桶一样使用 风投时,忘记添加属性文件来忽略列表可能是一件坏事。因为我们会向全世界公开我们的密码。问题依然存在。

相反,我们应该在项目的同一目录中创建属性文件,而不是在项目的外部创建。我们通过使用 gradle.properties 文件在外部创建它。

步骤如下:

1.在你的根项目上编辑或者创建 gradle.properties,并添加以下代码,记得用你自己的代码编辑路径:

AndroidProject.signing=/your/path/androidproject.properties

2. 在/your/path/中创建 androidproject.properties 并添加以下代码,不要忘记将/your/path/更改为/android.keystore 到 keystore 路径:

STORE_FILE=/your/path/to/android.keystore
STORE_PASSWORD=yourstorepassword
KEY_ALIAS=yourkeyalias
KEY_PASSWORD=yourkeypassword

3.在你的应用模块 build.gradle (不是你的项目根 build.gradle)中,如果不存在,添加以下代码或者对其进行调整:

signingConfigs {
release
}
buildTypes {
debug {
debuggable true
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}

4. 在步骤3的代码下添加以下代码:

if (project.hasProperty("AndroidProject.signing")
&& new File(project.property("AndroidProject.signing").toString()).exists()) {
def Properties props = new Properties()
def propFile = new File(project.property("AndroidProject.signing").toString())
if(propFile.canRead()) {
props.load(new FileInputStream(propFile))
if (props!=null && props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&
props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
} else {
println 'androidproject.properties found but some entries are missing'
android.buildTypes.release.signingConfig = null
}
} else {
println 'androidproject.properties file not found'
android.buildTypes.release.signingConfig = null
}
}

这段代码将在 步骤1Gradle 房地产公司中搜索 AndroidProject.sign 属性。如果找到属性,它会将属性值转换为指向我们在 第二步中创建的 androidproject.properties 的文件路径。然后,它的所有属性值将用作 build.gradle 的签名配置。

现在我们不需要再担心暴露密钥库密码的风险。

详情请浏览 签署 Android apk 而不在 build.gradle 中放入 keystore 信息

对于那些希望把他们的证书放在外部 JSON 文件中,然后从年级中读出来的人来说,这就是我所做的:

My _ project/credentials.json:

{
"android": {
"storeFile": "/path/to/acuity.jks",
"storePassword": "your_store_password",
"keyAlias": "your_android_alias",
"keyPassword": "your_key_password"
}
}

My _ project/android/app/build.gradle

// ...
signingConfigs {
release {


def credsFilePath = file("../../credentials.json").toString()
def credsFile = new File(credsFilePath, "").getText('UTF-8')
def json = new groovy.json.JsonSlurper().parseText(credsFile)
storeFile file(json.android.storeFile)
storePassword = json.android.storePassword
keyAlias = json.android.keyAlias
keyPassword = json.android.keyPassword
}
...
buildTypes {
release {
signingConfig signingConfigs.release //I added this
// ...
}
}
}
// ...
}

我之所以选择 .json文件类型,而不是 .properties文件类型(在公认的答案中) ,是因为我还想将其他数据(我需要的其他自定义属性)存储到同一个文件(my_project/credentials.json)中,并且仍然可以从该文件中解析签名信息。

可以从命令行获取任何现有的 Android Studio 分级项目,并在不编辑任何文件的情况下对其进行构建/签名。这样可以很好地将项目存储在版本控制中,同时将密钥和密码分开,而不是存储在 build.gradle 文件中:

./gradlew assembleRelease -Pandroid.injected.signing.store.file=$KEYFILE -Pandroid.injected.signing.store.password=$STORE_PASSWORD -Pandroid.injected.signing.key.alias=$KEY_ALIAS -Pandroid.injected.signing.key.password=$KEY_PASSWORD

这个问题已经收到了许多有效的答案,但我想分享我的代码,这可能是有用的 图书馆管理员,因为它 使原来的 build.gradle相当干净

我将一个文件夹添加到模块目录,我 gitignore。它看起来像这样:

/signing
/keystore.jks
/signing.gradle
/signing.properties

keystore.jkssigning.properties应该是不言自明的。 signing.gradle看起来像这样:

def propsFile = file('signing/signing.properties')
def buildType = "release"


if (!propsFile.exists()) throw new IllegalStateException("signing/signing.properties file missing")


def props = new Properties()
props.load(new FileInputStream(propsFile))


def keystoreFile = file("signing/keystore.jks")
if (!keystoreFile.exists()) throw new IllegalStateException("signing/keystore.jks file missing")


android.signingConfigs.create(buildType, {
storeFile = keystoreFile
storePassword = props['storePassword']
keyAlias = props['keyAlias']
keyPassword = props['keyPassword']
})


android.buildTypes[buildType].signingConfig = android.signingConfigs[buildType]

还有原版的 build.gradle

apply plugin: 'com.android.application'
if (project.file('signing/signing.gradle').exists()) {
apply from: 'signing/signing.gradle'
}


android {
compileSdkVersion 27
defaultConfig {
applicationId ...
}
}


dependencies {
implementation ...
}

正如您所看到的,您根本不需要指定 buildType,如果用户有权访问有效的 signing目录,他只需将其放入模块中,就可以构建一个有效的签名版本应用程序,否则它只是像通常那样为他工作。

我的密码包含一个特殊的字符,美元符号 $和我必须在 gradle.properties 文件中转义它。在那之后,签名对我来说很有用。

灵感来自 https://stackoverflow.com/a/33218300/1673761的一些改进

优点

  • Keystore 属性(文件名、别名、密码)存储在 git 之外
  • 即使没有 keystore 文件或 keystore.properties,调试模式仍然可以工作
  • 密钥存储库及其属性并排放在根文件夹中

在项目的根目录中(或者在“反应本机”的 android文件夹中) :

  • 添加生成的密钥存储库文件即: app.keystore
  • 创建一个名为 keystore.properties的文件
  • 将这两个文件添加到 .gitignore

keystore.properties中添加

STORE_FILE=app.keystore
KEY_ALIAS=app_alias
STORE_PASSWORD=your_password
KEY_PASSWORD=your_password

app/build.gradle中添加

// Load keystore
def keystoreProperties = new Properties()
try {
def keystorePropertiesFile = rootProject.file("keystore.properties");
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} catch(IOException e) {
// We don't have release keys, ignoring
}


...


android {
...
signingConfigs {
release {
if (keystoreProperties['STORE_FILE']) {
storeFile rootProject.file(keystoreProperties['STORE_FILE'])
storePassword keystoreProperties['STORE_PASSWORD']
keyAlias keystoreProperties['KEY_ALIAS']
keyPassword keystoreProperties['KEY_PASSWORD']
}
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}

PS: 欢迎编辑以改进常规逻辑

除非您使用 CI 工具将您的凭据以明文形式存储在文本文件中,否则不是一个好主意,即使该文件不受版本控制。

我知道这是常见的做法(甚至 官方文件提到它) ,但你甚至不会想到存储密码的个人电子邮件在一个文本文件,为什么你会这样做与您的应用程序(s) ?

我的方法是使用一个简单的 查询两个密码的命令并构建这个版本,同时尽可能保持 build.gradle 的清洁。例如:

Key store password:
Key password:

我看了看其他的答案,但他们要么作为明文 B)不工作或 C)存储凭据过于复杂的东西。


  1. 将这个签名配置添加到 build.gradle:

    android {
    ...
    signingConfigs {
    release {
    storeFile file(findProperty('keystore_path') ?: 'default path')
    storePassword findProperty('keystore_pw')
    keyAlias findProperty('key_alias')
    keyPassword findProperty('key_pw')
    }
    }
    ...
    
    
    buildTypes {
    release {
    ...
    signingConfig signingConfigs.release
    }
    }
    }
    
  2. 在项目目录中运行这个命令(编辑 /PATH/TO/KEYSTORE.jksKEY_ALIAS) :

    echo -n Key store password: && read -s storepw && echo && \
    echo -n Key password: && read -s keypw && echo && \
    ./gradlew assembleRelease -Pkeystore_path='/PATH/TO/KEYSTORE.jks' -Pkeystore_pw=$storepw -Pkey_alias='KEY_ALIAS' -Pkey_pw=$keypw
    

在‘ keystore.properties

    STORE_FILE = app.keystore
KEY_ALIAS = app_alias
STORE_PASSWORD = your_password
KEY_PASSWORD = your_password


app/build.gradle.kts中添加

...


android {
...
signingConfigs {
create("release") {
// Load keystore
val keystoreProperties = Properties().apply{
load(File("keystore.properties").reader())
}
storeFile = File(keystoreProperties.getProperty("STORE_FILE"))
storePassword = keystoreProperties.getProperty("STORE_PASSWORD")
keyAlias = keystoreProperties.getProperty("KEY_ALIAS")
keyPassword= keystoreProperties.getProperty("KEY_PASSWORD")
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}

这是 Kotlin 构建脚本(build.gradle.kts)不同于 Anand Damodaran 的回答的另一个答案。

它尝试从 本地物业文件读取,回退到操作系统环境变量。它在 GitHub Actions (您可以在存储库设置中创建环境机密)等 CI 中特别有用。

请注意,我使用的是 Kotlin 1.6.10和 Gradle 7.4.2以及 Android Gradle Plugin (AGP)7.0.4。

import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
// ...


val environment = System.getenv()
fun getLocalProperty(key: String) = gradleLocalProperties(rootDir).getProperty(key)
fun String.toFile() = File(this)


android {
signingConfigs {
create("MySigningConfig") {
keyAlias = getLocalProperty("signing.keyAlias") ?: environment["SIGNING_KEY_ALIAS"] ?: error("Error!")
storeFile = (getLocalProperty("signing.storeFile") ?: environment["SIGNING_STORE_FILE"] ?: error("Error!")).toFile()
keyPassword = getLocalProperty("signing.keyPassword") ?: environment["SIGNING_KEY_PASSWORD"] ?: error("Error!")
storePassword = getLocalProperty("signing.storePassword") ?: environment["SIGNING_STORE_PASSWORD"] ?: error("Error!")
enableV1Signing = true
enableV2Signing = true
}
}


buildTypes {
release {
signingConfig = signingConfigs["MySigningConfig"]
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}

如前所述,您可以在项目的根目录中放置一个 本地物业文件,其中包含属性的值:

signing.keyAlias=My key
signing.keyPassword=zyxwvuts
signing.storePassword=abcdefgh
signing.storeFile=C\:\\Users\\Mahozad\\keystore.jks

或者你可以在你的操作系统上设置/创建环境变量,例如创建一个名为 SIGNING_KEY_ALIAS run 的环境变量:

  • 命令提示符: setx SIGNING_KEY_ALIAS "My key"
  • Linux 终端: export SIGNING_KEY_ALIAS="My key"