Assine o APK sem colocar as informações do keystore no build.gradle

152

Estou tentando configurar o processo de assinatura para que a senha do keystore e a senha da chave não sejam armazenadas no build.gradlearquivo do projeto .

Atualmente, tenho o seguinte no build.gradle:

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

    buildTypes {
        release {
            signingConfig signingConfigs.release            
        }
    }
}

Funciona perfeitamente, mas não devo colocar os valores para storePassworde keyPasswordno meu repositório. Eu preferiria não colocar storeFilee keyAliaslá também.

Existe uma maneira de alterar o build.gradlearquivo para que ele obtenha senhas de alguma fonte externa (como um arquivo que reside apenas no meu computador)?

E, é claro, o alterado build.gradledeve ser utilizável em qualquer outro computador (mesmo que o computador não tenha acesso a senhas).

Estou usando o Android Studio e no Mac OS X Maverics, se isso importa.

Bobrovsky
fonte
"E, é claro, o build.gradle alterado deve ser utilizável em qualquer outro computador (mesmo que o computador não tenha acesso a senhas)" - se os dados não estiverem disponíveis build.gradle, você terá que ter algo diferente build.gradle, seja isso é um ajuste para variáveis ​​de ambiente (por uma resposta), um arquivo de propriedades (por outra resposta) ou algum outro meio. Se você não deseja ter coisas fora build.gradle, então, por definição, todas as informações de assinatura devem estar dentro buid.gradle .
CommonsWare
2
@CommonsWare Você está certo. Mas eu não disse que queria ter algo estritamente dentro do build.gradle. E eu fiz dizer que build.gradle poderia obter senhas de alguma fonte externa (como um arquivo que reside em apenas o meu computador
Bobrovsky
Eu sinalizei como uma duplicata de stackoverflow.com/questions/18328730/… , com base em meta.stackoverflow.com/questions/311044/…
user2768

Respostas:

120

O bom do Groovy é que você pode misturar livremente o código Java, e é muito fácil ler em um arquivo de chave / valor usando java.util.Properties. Talvez exista uma maneira ainda mais fácil de usar o Groovy idiomático, mas o Java ainda é bastante simples.

Crie um keystore.propertiesarquivo (neste exemplo, no diretório raiz do seu projeto ao lado de settings.gradle, embora você possa colocá-lo onde quiser:

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

Adicione isto ao seu 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']
        }
    }
}
Scott Barta
fonte
29
Eu tinha que remover as citações dos meus keystore.properties
Jacob Tabak
6
Ele não gera a versão assinada para mim com a versão do plugin 0.9. +. O que devo fazer com o bloco signatureConfigs e o item buildTypes.release.signingConfig? Remova eles?
Fernando Gallego
1
Parece que definir storeFile com qualquer valor válido (por exemplo storeFile file('AndroidManifest.xml')) e depois substituí-lo faz com que o processo de assinatura ocorra.
miracle2k
5
A construção resulta em um erro em Error:(24, 0) Could not find property 'android' on root project 'RootProjectName'que a linha 24 é a que contém o bloco if. A adição apply plugin: 'com.android.application'à raiz build.gradle também permite que a construção falhe. O que estou fazendo de errado?
PhilLab
2
Isso não funciona no ano de 2018. Isso deve ser preterido? Mantido jogando erro queCould not get unknown property 'android' for root project
Neon Warge
106

Como alternativa, se você deseja aplicar a resposta de Scott Barta de maneira mais semelhante ao código de classificação gerado automaticamente, você pode criar um keystore.propertiesarquivo na pasta raiz do projeto:

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

e modifique seu código de classificação para:

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

    ...

}

Você pode armazenar esse arquivo de propriedades na raiz do seu módulo, nesse caso, apenas omitir rootProjecte também pode modificar esse código para ter vários conjuntos de propriedades para diferentes keystores e aliases de chave.

CurlyCorvus
fonte
7
Funciona bem. Eu costumava if ( keystorePropertiesFile.exists() )garantir que o arquivo estivesse presente antes de tentar obter os atributos e tentar assinar.
Joshua Pinter
E não se esqueça de adicionar .txtextensão no final do keystore.propertiesarquivo.
Levon Petrosyan
11
Você não precisa de uma .txtextensão no keystore.propertiesarquivo.
precisa
2
Parece que esta informação foi adicionada aqui - developer.android.com/studio/publish/…
Vadim Kotov
36

A maneira mais fácil é criar um ~/.gradle/gradle.propertiesarquivo.

ANDROID_STORE_PASSWORD=hunter2
ANDROID_KEY_PASSWORD=hunter2

Então seu build.gradlearquivo pode ficar assim:

android {
    signingConfigs {
        release {
            storeFile file('yourfile.keystore')
            storePassword ANDROID_STORE_PASSWORD
            keyAlias 'youralias'
            keyPassword ANDROID_KEY_PASSWORD
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}
Dan Fabulich
fonte
1
Devo gitignore ~ ​​/ .gradle / gradle.properties?
vzhen
As instruções completas também estão na documentação nativa do react.
Pencilcheck
23

Depois de ler alguns links:

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

Como você está usando o Mac OSX, pode usar o Keychain Access para armazenar suas senhas.

Como adicionar senha no Acesso às Chaves

Em seguida, em seus scripts de classificação:

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

Use assim:

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
        }
    }
}
Jared Burrows
fonte
2
embora sua resposta se aplique apenas ao Mac OSX, eu realmente gosto! Observe que o segundo link que você forneceu contém uma solução de backup para outras plataformas, caso alguém precise implementar o suporte multiplataforma.
Delblanco 23/07
Você também pode fornecer a mesma solução para linux e windows? Obrigado.
Jay Mungara
18

É assim que eu faço. Usar variáveis ​​de ambiente

  signingConfigs {
    release {
        storeFile file(System.getenv("KEYSTORE"))
        storePassword System.getenv("KEYSTORE_PASSWORD")
        keyAlias System.getenv("KEY_ALIAS")
        keyPassword System.getenv("KEY_PASSWORD")
    }
Madhur Ahuja
fonte
3
Infelizmente, isso exige que ambientes do sistema sejam criados para cada projeto em cada computador . Caso contrário, recebo o seguinte erroNeither path nor baseDir may be null or empty string. path='null'
Bobrovsky
@Bobrovsky Eu sei que esta pergunta foi respondida, mas você pode usar variáveis ​​de ambiente do sistema ou o arquivo gradle.properties. Você provavelmente deseja usar o arquivo gradle.properties. Você pode usá-lo para vários projetos.
amigos estão dizendo sobre
3
isso não funciona no MacOSX, a menos que você execute o Android Studio na linha de comando.
Henrique de Sousa
Eu concordo com tudo acima. Eu tenho a mesma configuração e você não pode compilar isso no estúdio Android. Você precisa executar a partir da linha de comando para que isso funcione. Estou procurando uma maneira melhor, para não precisar comentar essas linhas quando executo o Android Studio.
Sayooj Valsan
@Bobrovsky: Funciona no Windows ?. Devemos mencionar tudo isso em ambientes de sistema?
DKV
12

É possível pegar qualquer projeto de classificação existente do Android Studio e compilar / assinar a partir da linha de comando sem editar nenhum arquivo. Isso torna muito bom armazenar seu projeto no controle de versão, mantendo suas chaves e senhas separadas e não no seu arquivo 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
Wayne Piekarski
fonte
9

A resposta aceita usa um arquivo para controlar qual keystore usar para assinar o APK que reside na mesma pasta raiz do projeto. Quando usamos vcs como Git , pode ser uma coisa ruim quando esquecemos de adicionar o arquivo de propriedades à lista de ignorados. Porque divulgaremos nossa senha para o mundo. Os problemas ainda persistem.

Em vez de criar o arquivo de propriedades no mesmo diretório do nosso projeto, devemos fazê-lo fora. Nós saímos usando o arquivo gradle.properties.

Aqui estão os passos:

1. Edite ou crie gradle.properties em seu projeto raiz e adicione o seguinte código, lembre-se de editar o caminho com seu próprio:

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

2.Crie androidproject.properties em / your / path / e adicione o seguinte código a ele, não esqueça de alterar /your/path/to/android.keystore no caminho do keystore:

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

3. No módulo de aplicativo build.gradle (não na raiz do projeto build.gradle), adicione o seguinte código, se não existir, ou ajuste-o:

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

4. Adicione o seguinte código abaixo do código na etapa 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  
     }  
   }  

Este código procurará a propriedade AndroidProject.signing em gradle.properties na etapa 1 . Se a propriedade for encontrada, ela converterá o valor da propriedade como caminho do arquivo, apontando para androidproject.properties que criamos na etapa 2 . Então, todo o valor da propriedade será usado como configuração de assinatura para o nosso build.gradle.

Agora não precisamos nos preocupar novamente com o risco de expor nossa senha do keystore.

Leia mais em Signing Android apk sem colocar informações do keystore no build.gradle

ישו אוהב אותך
fonte
Isso funciona bem para mim. só para saber por que eles estão usando o arquivo storeFile (System.getenv ("KEYSTORE")))
DKV
9

Para aqueles que procuram colocar suas credenciais em um arquivo JSON externo e ler que da graduação foi o que eu fiz:

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

A razão pela qual escolhi um .jsontipo de arquivo, e não um .propertiestipo de arquivo (como na resposta aceita), é porque eu também queria armazenar outros dados (outras propriedades personalizadas necessárias) no mesmo arquivo ( my_project/credentials.json) e ainda ter o gradle analise o assinando informações de dentro desse arquivo também.

SudoPlz
fonte
Parece a melhor solução para mim.
de aspiração Dev
4

Esta pergunta recebeu muitas respostas válidas, mas eu queria compartilhar meu código, que pode ser útil para mantenedores de bibliotecas , porque deixa o original build.gradlebastante limpo .

Eu adiciono uma pasta ao diretório do módulo que eu gitignore. Se parece com isso:

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

keystore.jkse signing.propertiesdeve ser auto-explicativo. E signing.gradlefica assim:

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]

E o original 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 ...
}

Como você pode ver, você não precisa especificar os buildTypes, se o usuário tiver acesso a um signingdiretório válido , ele apenas o coloca no módulo e ele pode criar um aplicativo de versão assinado válido, caso contrário, ele funciona para ele como normalmente faria.

Michał K
fonte
Eu realmente gosto desta solução. Observe, no entanto, que apply fromdeve vir após o androidbloqueio
mgray88
0

Você pode solicitar senhas na linha de comando:

...

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
  }

  ...
}

...

Esta resposta apareceu anteriormente: https://stackoverflow.com/a/33765572/3664487

user2768
fonte
Embora esse link possa responder à pergunta, é melhor incluir aqui as partes essenciais da resposta e fornecer o link para referência. As respostas somente para links podem se tornar inválidas se a página vinculada for alterada. - Do comentário
mkobit
1
@mkobit, este é um link para o conteúdo no Stack Overflow! É claro que eu poderia copiar e colar o conteúdo vinculado, mas isso leva à duplicação do conteúdo. Portanto, assumo e me corrijo se estiver errado, postar um link é a melhor solução. Qualquer argumento de que "a página vinculada seja alterada" deve ser descartado, com base no fato de que o conteúdo aqui também pode ser alterado. Eu recomendo fortemente contra a sua solução para excluir! Porque o conteúdo vinculado fornece uma excelente solução.
user2768
Bem, acho que o problema é que essa ainda é uma resposta "somente link". Acho que a solução é publicá-la como um comentário, sinalizar a pergunta como duplicada ou escrever aqui uma nova resposta que resolva o problema.
Mkobit
Respostas "somente link" devem ser incentivadas em alguns casos. No entanto, segui seus conselhos e conteúdo duplicado. (Conteúdo duplicado é claramente problemática, porque algum conteúdo pode ser atualizado, enquanto o conteúdo restante pode não ser.)
user2768
Acabei de atualizar a resposta e o conteúdo duplicado está causando problemas! Se houver uma política contra respostas somente de link, ela deverá ser adaptada para considerar esses casos de esquina.
user2768
0

Minha senha continha um caractere especial cujo cifrão $ e eu tive que escapar disso no arquivo gradle.properties. Depois disso, a assinatura funcionou para mim.

Yogendra Ghatpande
fonte