在不将密钥库信息放入build.gradle的情况下签署APK


152

我试图设置签名过程,以便密钥库密码和密钥密码存储在项目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            
        }
    }
}

它的工作原理完全没有问题,但我不能把值的storePassword,而且keyPassword在我的仓库。我宁愿不把storeFilekeyAlias有两种。

有没有一种方法可以更改build.gradle密码,以便它可以从某些外部来源(例如仅位于我的计算机上的文件)获取密码?

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

如果有问题,我正在使用Android Studio和Mac OS X Maverics。


“而且,当然,更改后的build.gradle应该可以在任何其他计算机上使用(即使该计算机无法访问密码)”-如果数据不在其中,则无论是否build.gradle存在,您都必须拥有其他内容build.gradle。这是对环境变量(每个答案),属性文件(另一个答案)或其他某种方式的调整。如果您不愿意在外部放置东西build.gradle,那么根据定义,所有签名信息都必须在内部 buid.gradle
CommonsWare 2013年

2
@CommonsWare你是对的。不过,我没有告诉我要在build.gradle中严格包含任何内容。而且我也告诉大家,可能的build.gradle 从一些外部源获取密码(如文件仅驻留在我的电脑
Bobrovsky


Answers:


120

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']
        }
    }
}

29
我不得不从我的keystore.properties中删除引号
Jacob Tabak 2014年

6
插件版本0.9。+不会为我生成签名版本。我应该如何处理signingConfigs块和buildTypes.release.signingConfig项目?删除它们?
Fernando Gallego 2014年

1
似乎将storeFile设置为任何有效值(例如storeFile file('AndroidManifest.xml')),然后覆盖它会导致签名过程发生。
miracle2k

5
构建会导致错误Error:(24, 0) Could not find property 'android' on root project 'RootProjectName',其中第24行是带有if块的那一行。添加apply plugin: 'com.android.application'到根目录build.gradle也会使构建失败。我究竟做错了什么?
PhilLab 2015年

2
这在2018年不起作用。必须弃用吗?保持错误提示Could not get unknown property 'android' for root project
Neon Warge

106

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

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

并将您的gradle代码修改为:

// 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,你也可以修改这个代码,有几套不同的密钥存储和密钥别名属性。


7
效果很好。if ( keystorePropertiesFile.exists() )在尝试获取属性并尝试签名之前,我曾经确保文件存在。
Joshua Pinter

并且不要忘记.txtkeystore.properties文件末尾添加扩展名 。
莱文·彼得罗森

11
您不需要文件.txt扩展名keystore.properties
Matt Zukowski

2
像这样的信息看起来在这里添加- developer.android.com/studio/publish/...
瓦迪姆·科托夫

36

最简单的方法是创建~/.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
        }
    }
}

1
我应该gitignore〜/ .gradle / gradle.properties吗?
vzhen

完整说明也包含在react本机文档中。
Pencilcheck

23

阅读一些链接后:

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

由于使用的是Mac OSX,因此可以使用“钥匙串访问”来存储密码。

如何在钥匙串访问中添加密码

然后在您的gradle脚本中:

/* 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
        }
    }
}

2
尽管您的答案仅适用于Mac OSX,但我真的很喜欢!请注意,如果有人需要实现多平台支持,则您提供的第二个链接包含其他平台的备份解决方案。
Delblanco 2014年

您能否同时为Linux和Windows提供相同的解决方案?谢谢。
杰伊·蒙加拉

18

这就是我的方法。使用环境变量

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

3
不幸的是,这要求为每台计算机上的每个项目创建系统环境。否则,我会收到以下错误Neither path nor baseDir may be null or empty string. path='null'
Bobrovsky 2013年

@Bobrovsky我知道这个问题已经解决了,但是您可以使用系统环境变量或gradle.properties文件。您可能要使用gradle.properties文件。您可以将其用于多个项目。
Jared Burrows 2014年

3
除非您从命令行运行Android Studio,否则这在MacOSX上不起作用。
Henrique de Sousa

我同意以上所有观点。我具有相同的配置,因此您无法在Android Studio中进行编译。您需要从命令行运行才能正常工作。我正在寻找一种更好的方法,以便在Android Studio中运行时不必注释这些行。
Sayooj Valsan '16

@Bobrovsky:它可以在Windows上运行吗?我们应该在系统环境中提到所有这些吗?
DKV

12

可以采用任何现有的Android Studio gradle项目并从命令行构建/签名,而无需编辑任何文件。这非常适合将项目存储在版本控制中,同时将密钥和密码分开而不是放在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

9

接受的答案使用文件来控制使用哪个密钥库来对驻留在项目相同根文件夹中的APK进行签名。当我们使用诸如Git之类的vcs时,如果忘记添加属性文件以忽略列表,则可能是一件坏事。因为我们会将密码泄露给全世界。问题仍然存在。

与其将属性文件放置在项目内的同一目录中,不如将其放置在外部。我们使用gradle.properties文件将其放置在外部。

步骤如下:

1.在根项目上编辑或创建gradle.properties并添加以下代码,请记住使用自己的路径进行编辑:

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

2.在/ your / path /中创建androidproject.properties并添加以下代码,不要忘记将/your/path/to/android.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  
     }  
   }  

此代码将从步骤1开始gradle.properties中搜索AndroidProject.signing属性。如果找到该属性,它将把属性值转换为指向我们在步骤2中创建的androidproject.properties的文件路径。然后,其中的所有属性值都将用作build.gradle的签名配置。

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

不将密钥库信息放入build.gradle的Signing Android apk中阅读更多内容


这对我来说很好。只知道他们为什么使用storeFile file(System.getenv(“ KEYSTORE”))
DKV

9

对于那些希望将其凭据放入外部JSON文件并从gradle中读取内容的人,这是我所做的:

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),并且仍然具有gradle解析也从该文件中签名信息。


似乎是我的最佳解决方案。
有抱负的开发

4

这个问题已经得到了很多有效的答案,但是我想分享我的代码,这对于库维护者可能是有用的,因为它使原始代码build.gradle非常干净

我在模块目录中添加一个文件夹gitignore。看起来像这样:

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

keystore.jks并且signing.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 ...
}

如您所见,您根本不需要指定buildTypes,如果用户有权访问有效signing目录,则只需将其放在模块中即可构建有效的签名发布应用程序,否则它将对他有效它通常会这样做。


我真的很喜欢这个解决方案。但是请注意,apply from应该在android屏蔽之后
mgray88

0

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

...

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


尽管此链接可以回答问题,但最好在此处包括答案的基本部分,并提供链接以供参考。如果链接的页面发生更改,仅链接的答案可能会失效。- 点评
mkobit

1
@mkobit,这是堆栈溢出内容的链接!我当然可以复制并粘贴链接的内容,但这会导致重复的内容。因此,我认为,如果我错了,请纠正我,发布链接是最好的解决方案。基于此处的内容也可能会更改的任何有关“链接页面更改”的争论都应予以驳回。我强烈建议您删除该解决方案!因为链接的内容提供了很好的解决方案。
user2768

好吧,我认为问题在于这仍然是“仅链接”的答案。我认为解决方案是将其发布为评论,将问题标记为重复项,或在此处写出解决该问题的新答案。
mkobit

在某些情况下,应鼓励“仅链接”答案。不过,我已经听取了您的建议并重复了内容。(重复的内容显然存在问题,因为某些内容可能会被更新,而其余的内容可能不会。)
user2768 2015年

确实,我刚刚更新了答案,重复的内容引起了问题!如果存在针对仅链接的答案的政策,则应对其进行调整以考虑此类极端情况。
user2768

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.