Firme APK sin poner información del almacén de claves en build.gradle

152

Estoy tratando de configurar el proceso de firma para que la contraseña del almacén de claves y la clave no se almacenen en el build.gradlearchivo del proyecto .

Actualmente tengo lo siguiente en el 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 perfectamente bien, pero no debo poner los valores para storePassword, y keyPassworden mi repositorio. Preferiría no poner storeFiley keyAliasallí tampoco.

¿Hay alguna manera de alterarlo build.gradlepara que obtenga contraseñas de alguna fuente externa (como un archivo que reside solo en mi computadora)?

Y, por supuesto, la alteración build.gradledebe ser utilizable en cualquier otra computadora (incluso si la computadora no tiene acceso a las contraseñas).

Estoy usando Android Studio y en Mac OS X Maverics si es importante.

Bobrovsky
fuente
"Y, por supuesto, el build.gradle alterado debería poder utilizarse en cualquier otra computadora (incluso si la computadora no tiene acceso a las contraseñas)" - si los datos no se encuentran build.gradle, tendrá que tener algo más que build.gradle, si eso es un ajuste a las variables de entorno (por una respuesta), un archivo de propiedades (por otra respuesta) o algún otro medio. Si no está dispuesto a tener cosas fuera build.gradle, entonces, por definición, toda la información de firma debe estar dentro buid.gradle .
CommonsWare
2
@CommonsWare Tienes razón. Sin embargo, no dije que quiero tener algo estrictamente dentro del build.gradle. Y sí dije que build.gradle podría obtener contraseñas de alguna fuente externa (como un archivo que reside solo en mi computadora
Bobrovsky

Respuestas:

120

Lo bueno de Groovy es que puedes mezclar libremente código Java, y es bastante fácil de leer en un archivo de clave / valor java.util.Properties. Quizás haya una forma aún más fácil de usar Groovy idiomático, pero Java sigue siendo bastante simple.

Cree un keystore.propertiesarchivo (en este ejemplo, en el directorio raíz de su proyecto al lado settings.gradle, aunque puede colocarlo donde desee:

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

Agregue esto a su 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
fuente
29
Tuve que eliminar las citas de mi tienda de claves
Jacob Tabak
66
No genera la versión firmada para mí con la versión del complemento 0.9. +. ¿Qué se supone que debo hacer con el bloque SignatureConfigs y el elemento buildTypes.release.signingConfig? eliminarlos?
Fernando Gallego
1
Parece que establecer storeFile en cualquier valor válido (p storeFile file('AndroidManifest.xml'). Ej. ) Y luego anularlo ocasiona que tenga lugar el proceso de firma.
miracle2k
55
La construcción da como resultado un error Error:(24, 0) Could not find property 'android' on root project 'RootProjectName'donde la línea 24 es la que tiene el bloque if. Agregar apply plugin: 'com.android.application'a la raíz build.gradle también permite que falle la compilación. ¿Qué estoy haciendo mal?
PhilLab
2
Esto no funciona en el año 2018. ¿Esto debe estar en desuso? Se mantuvo el error de lanzamiento queCould not get unknown property 'android' for root project
Neon Warge
106

Alternativamente, si desea aplicar la respuesta de Scott Barta de una manera más similar al código gradle generado automáticamente, puede crear un keystore.propertiesarchivo en la carpeta raíz del proyecto:

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

y modifique su código gradle 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']
        }
    }

    ...

}

Puede almacenar este archivo de propiedades en la raíz de su módulo, en cuyo caso simplemente omita rootProject, y también puede modificar este código para tener varios conjuntos de propiedades para diferentes almacenes de claves y alias de claves.

CurlyCorvus
fuente
77
Funciona genial. Solía if ( keystorePropertiesFile.exists() )asegurarme de que el archivo esté presente antes de intentar obtener los atributos e intentar firmar.
Joshua Pinter
Y no olvide agregar .txtextensión al final del keystore.propertiesarchivo.
Levon Petrosyan
11
No debería necesitar una .txtextensión en el keystore.propertiesarchivo.
Matt Zukowski el
2
Parece que esta información se agregó aquí - developer.android.com/studio/publish/…
Vadim Kotov
36

La forma más fácil es crear un ~/.gradle/gradle.propertiesarchivo.

ANDROID_STORE_PASSWORD=hunter2
ANDROID_KEY_PASSWORD=hunter2

Entonces su build.gradlearchivo puede verse así:

android {
    signingConfigs {
        release {
            storeFile file('yourfile.keystore')
            storePassword ANDROID_STORE_PASSWORD
            keyAlias 'youralias'
            keyPassword ANDROID_KEY_PASSWORD
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}
Dan Fabulich
fuente
1
¿Debo gitignore ~ ​​/ .gradle / gradle.properties?
vzhen
Las instrucciones completas también se encuentran en la documentación nativa de reacción.
Pencilcheck
23

Después de leer algunos enlaces:

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

Como está usando Mac OSX, puede usar el Acceso a Llaveros para almacenar sus contraseñas.

Cómo agregar contraseña en Keychain Access

Luego, en sus guiones 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]
}

Usar así:

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
fuente
2
aunque su respuesta solo se aplica a Mac OSX, ¡realmente me gusta! Tenga en cuenta que el segundo enlace que proporcionó contiene una solución de respaldo para otras plataformas, en caso de que alguien necesite implementar soporte multiplataforma.
Delblanco
¿Puede proporcionar la misma solución para Linux y Windows también? Gracias.
Jay Mungara
18

Así es como lo hago. Usar variables de entorno

  signingConfigs {
    release {
        storeFile file(System.getenv("KEYSTORE"))
        storePassword System.getenv("KEYSTORE_PASSWORD")
        keyAlias System.getenv("KEY_ALIAS")
        keyPassword System.getenv("KEY_PASSWORD")
    }
Madhur Ahuja
fuente
3
Desafortunadamente, esto requiere que se creen entornos de sistema para cada proyecto en cada computadora . De lo contrario, aparece el siguiente errorNeither path nor baseDir may be null or empty string. path='null'
Bobrovsky,
@Bobrovsky Sé que esta pregunta está respondida, pero puede usar variables de entorno del sistema o el archivo gradle.properties. Probablemente desee utilizar el archivo gradle.properties. Puedes usarlo para múltiples proyectos.
Jared Burrows
3
esto no funciona en MacOSX a menos que ejecute Android Studio desde la línea de comandos.
Henrique de Sousa
Estoy de acuerdo con todo lo anterior. Tengo la misma configuración y no puedes compilarla en el estudio de Android. Debe ejecutar desde la línea de comandos para que esto funcione. Estoy buscando una mejor manera, para no tener que comentar estas líneas cuando ejecuto Android Studio.
Sayooj Valsan
@Bobrovsky: ¿Funciona en Windows? ¿Deberíamos mencionar todo esto en entornos de sistema?
DKV
12

Es posible tomar cualquier proyecto de gradle de Android Studio existente y construirlo / firmarlo desde la línea de comandos sin editar ningún archivo. Esto lo hace muy agradable para almacenar su proyecto en el control de versiones mientras mantiene sus claves y contraseñas separadas y no en su archivo 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
fuente
9

La respuesta aceptada utiliza un archivo para controlar qué almacén de claves usar para firmar el APK que reside en la misma carpeta raíz del proyecto. Cuando usamos vcs como Git , podría ser algo malo cuando olvidamos agregar el archivo de propiedades para ignorar la lista. Porque revelaremos nuestra contraseña al mundo. Los problemas aún persisten.

En lugar de hacer el archivo de propiedades en el mismo directorio dentro de nuestro proyecto, deberíamos hacerlo fuera. Lo hacemos afuera usando el archivo gradle.properties.

Aquí los pasos:

1.Edite o cree gradle.properties en su proyecto raíz y agregue el siguiente código, recuerde editar la ruta con la suya:

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

2.Cree androidproject.properties en / your / path / y agregue el siguiente código, no olvide cambiar /your/path/to/android.keystore a su ruta de almacén de claves:

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

3.En el módulo de su aplicación build.gradle (no en la raíz del proyecto build.gradle) agregue el siguiente código si no existe o ajústelo:

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

4. Agregue el siguiente código debajo del código en el paso 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 buscará la propiedad AndroidProject.signing en gradle.properties desde el paso 1 . Si se encuentra la propiedad, traducirá el valor de la propiedad como la ruta del archivo que apunta a androidproject.properties que creamos en el paso 2 . Luego, todo el valor de la propiedad se utilizará como configuración de firma para nuestro build.gradle.

Ahora no debemos preocuparnos nuevamente por el riesgo de exponer nuestra contraseña del almacén de claves.

Obtenga más información en Signing Android apk sin poner la información del almacén de claves en build.gradle

ישו אוהב אותך
fuente
Esto funciona bien para mí. solo para saber por qué están usando el archivo storeFile (System.getenv ("KEYSTORE"))
DKV
9

Para aquellos que buscan poner sus credenciales en un archivo JSON externo y leer eso desde el principio, esto es lo que hice:

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

La razón por la que elegí un .jsontipo de archivo, y no un .propertiestipo de archivo (como en la respuesta aceptada), es porque también quería almacenar otros datos (otras propiedades personalizadas que necesitaba) en ese mismo archivo ( my_project/credentials.json), y todavía tengo que analizar el firmando información desde ese archivo también.

SudoPlz
fuente
Parece la mejor solución para mí.
Aspirante a Dev
4

Esta pregunta ha recibido muchas respuestas válidas, pero quería compartir mi código que puede ser útil para los encargados del mantenimiento de la biblioteca , ya que deja el original build.gradlebastante limpio .

Agrego una carpeta al directorio del módulo que yo gitignore. Se parece a esto:

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

keystore.jksy signing.propertiesdebe explicarse por sí mismo. Y se signing.gradleve así:

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]

Y el 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 puede ver, no tiene que especificar los buildTypes en absoluto, si el usuario tiene acceso a un signingdirectorio válido , simplemente lo coloca en el módulo y puede construir una aplicación de lanzamiento firmada válida, de lo contrario, simplemente funciona para él como normalmente lo haría.

Michał K
fuente
Realmente me gusta esta solución. Sin embargo, apply fromtenga en cuenta que debería aparecer después del androidbloqueo
mgray88
0

Puede solicitar contraseñas desde la línea 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 respuesta apareció anteriormente: https://stackoverflow.com/a/33765572/3664487

usuario2768
fuente
Si bien este enlace puede responder la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden volverse inválidas si la página vinculada cambia. - De la opinión
mkobit
1
@mkobit, ¡este es un enlace al contenido en Stack Overflow! Podría, por supuesto, copiar y pegar el contenido vinculado, pero eso conduce a contenido duplicado. Por lo tanto, supongo y corríjome si estoy equivocado, publicar un enlace es la mejor solución. Cualquier argumento de que "la página vinculada cambia" debe descartarse, sobre la base de que el contenido aquí también podría cambiar. ¡Recomiendo su solución para eliminar! Porque el contenido vinculado proporciona una excelente solución.
user2768
Bueno, creo que el problema es que esta sigue siendo una respuesta "solo de enlace". Creo que la solución es publicarlo como un comentario, marcar la pregunta como un duplicado o escribir una nueva respuesta aquí que aborde el problema.
mkobit
Se deben alentar las respuestas "solo de enlace" en algunos casos. Sin embargo, he seguido tus consejos y contenido duplicado. (Contenido duplicado es claramente problemático, debido a que algunos contenidos podrían ser actualizados, mientras que el contenido restante podría no ser.)
user2768
De hecho, ¡acabo de actualizar la respuesta y el contenido duplicado está causando problemas! Si existe una política contra las respuestas de solo enlace, entonces debe adaptarse para considerar tales casos de esquina.
user2768
0

Mi contraseña contenía un carácter especial cuyo signo de dólar $ y tuve que escapar de eso en el archivo gradle.properties. Después de eso, la firma funcionó para mí.

Yogendra Ghatpande
fuente