Signez l'APK sans mettre les informations du keystore dans build.gradle

152

J'essaie de configurer le processus de signature afin que le mot de passe du fichier de clés et le mot de passe clé ne soient pas stockés dans le build.gradlefichier du projet .

Actuellement, j'ai ce qui suit dans le build.gradle:

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

    buildTypes {
        release {
            signingConfig signingConfigs.release            
        }
    }
}

Cela fonctionne parfaitement mais je ne dois pas mettre les valeurs pour le storePassword, et keyPassworddans mon référentiel. Je préférerais ne pas mettre storeFileet keyAliaslà non plus.

Existe-t-il un moyen de modifier le build.gradleafin qu'il obtienne les mots de passe d'une source externe (comme un fichier résidant uniquement sur mon ordinateur)?

Et bien sûr, le modifié build.gradledevrait être utilisable sur n'importe quel autre ordinateur (même si l'ordinateur n'a pas accès aux mots de passe).

J'utilise Android Studio et Mac OS X Maverics si cela compte.

Bobrovsky
la source
"Et bien sûr, le build.gradle modifié devrait être utilisable sur n'importe quel autre ordinateur (même si l'ordinateur n'a pas accès aux mots de passe)" - si les données ne sont pas là build.gradle, vous devrez avoir autre chose que build.gradle, si c'est un ajustement des variables d'environnement (pour une réponse), un fichier de propriétés (pour une autre réponse) ou d'autres moyens. Si vous ne souhaitez pas avoir des choses en dehors de build.gradle, alors par définition, toutes les informations de signature doivent être à l' intérieur buid.gradle .
CommonsWare
2
@CommonsWare Vous avez raison. Cependant, je n'ai pas dit que je voulais avoir quoi que ce soit strictement dans le build.gradle. Et j'ai dit que build.gradle pouvait obtenir des mots de passe à partir d'une source externe (comme un fichier qui réside uniquement sur mon ordinateur
Bobrovsky
J'ai signalé un doublon de stackoverflow.com/questions/18328730/… , sur la base de meta.stackoverflow.com/questions/311044/…
user2768

Réponses:

120

La bonne chose à propos de Groovy est que vous pouvez librement mélanger du code Java, et il est assez facile à lire dans un fichier clé / valeur en utilisant java.util.Properties. Il existe peut-être un moyen encore plus simple d'utiliser Groovy idiomatique, mais Java est toujours assez simple.

Créez un keystore.propertiesfichier (dans cet exemple, dans le répertoire racine de votre projet à côté de settings.gradle, bien que vous puissiez le placer où vous le souhaitez:

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

Ajoutez ceci à votre 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
la source
29
J'ai dû supprimer les citations de mon keystore.properties
Jacob Tabak
6
Il ne génère pas la version signée pour moi avec la version du plugin 0.9. +. Que suis-je censé faire avec le bloc signatureConfigs et l'élément buildTypes.release.signingConfig? retirez-les?
Fernando Gallego
1
Il semble que la définition de storeFile sur n'importe quelle valeur valide (par exemple storeFile file('AndroidManifest.xml')), puis sa substitution ultérieure provoque le processus de signature.
miracle2k
5
La construction entraîne une erreur Error:(24, 0) Could not find property 'android' on root project 'RootProjectName'où la ligne 24 est celle avec le bloc if. L'ajout apply plugin: 'com.android.application'à la racine build.gradle permet également à la construction d'échouer. Qu'est-ce que je fais mal?
PhilLab
2
Cela ne fonctionne pas sur l'année 2018. Cela doit être obsolète? Erreur de lancement conservée quiCould not get unknown property 'android' for root project
Neon Warge
107

Alternativement, si vous souhaitez appliquer la réponse de Scott Barta d'une manière plus similaire au code de gradle généré automatiquement, vous pouvez créer un keystore.propertiesfichier dans le dossier racine de votre projet:

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

et modifiez votre code de note pour:

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

    ...

}

Vous pouvez stocker ce fichier de propriétés à la racine de votre module, auquel cas omettez simplement rootProject, et vous pouvez également modifier ce code pour avoir plusieurs ensembles de propriétés pour différents keystores et alias de clé.

CurlyCorvus
la source
7
Fonctionne très bien. J'avais l'habitude if ( keystorePropertiesFile.exists() )de m'assurer que le fichier est présent avant d'essayer d'obtenir les attributs et d'essayer de signer.
Joshua Pinter
Et n'oubliez pas d'ajouter l' .txtextension à la fin du keystore.propertiesfichier.
Levon Petrosyan
11
Vous ne devriez pas avoir besoin d'une .txtextension sur le keystore.propertiesfichier.
Matt Zukowski
2
On dirait que cette information a été ajoutée ici - developer.android.com/studio/publish/…
Vadim Kotov
36

Le moyen le plus simple est de créer un ~/.gradle/gradle.propertiesfichier.

ANDROID_STORE_PASSWORD=hunter2
ANDROID_KEY_PASSWORD=hunter2

Ensuite, votre build.gradlefichier peut ressembler à ceci:

android {
    signingConfigs {
        release {
            storeFile file('yourfile.keystore')
            storePassword ANDROID_STORE_PASSWORD
            keyAlias 'youralias'
            keyPassword ANDROID_KEY_PASSWORD
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}
Dan Fabulich
la source
1
Dois-je gitignore ~ ​​/ .gradle / gradle.properties?
vzhen
Les instructions complètes sont également dans la documentation native de React.
Pencilcheck
23

Après avoir lu quelques liens:

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

Puisque vous utilisez Mac OSX, vous pouvez utiliser l'accès au trousseau pour stocker vos mots de passe.

Comment ajouter un mot de passe dans Keychain Access

Puis dans vos scripts 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]
}

Utilisez comme ceci:

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
la source
2
bien que votre réponse ne s'applique qu'à Mac OSX, je l'aime vraiment! Notez que le deuxième lien que vous avez fourni contient une solution de sauvegarde pour d'autres plates-formes, au cas où quelqu'un aurait besoin de mettre en œuvre le support multi-plates-formes.
Delblanco
Pouvez-vous fournir la même solution pour Linux et Windows? Merci.
Jay Mungara
18

Voilà comment je fais. Utiliser des variables d'environnement

  signingConfigs {
    release {
        storeFile file(System.getenv("KEYSTORE"))
        storePassword System.getenv("KEYSTORE_PASSWORD")
        keyAlias System.getenv("KEY_ALIAS")
        keyPassword System.getenv("KEY_PASSWORD")
    }
Madhur Ahuja
la source
3
Malheureusement, cela nécessite la création d'environnements système pour chaque projet sur chaque ordinateur . Sinon, j'obtiens l'erreur suivanteNeither path nor baseDir may be null or empty string. path='null'
Bobrovsky
@Bobrovsky Je sais que cette question a une réponse, mais vous pouvez utiliser des variables d'environnement système ou le fichier gradle.properties. Vous souhaitez probablement utiliser le fichier gradle.properties. Vous pouvez l'utiliser pour plusieurs projets.
Jared Burrows
3
cela ne fonctionne pas sur MacOSX sauf si vous exécutez Android Studio à partir de la ligne de commande.
Henrique de Sousa
Je suis d'accord avec tout ce qui précède. J'ai la même configuration et vous ne pouvez pas compiler cela dans le studio Android. Vous devez exécuter à partir de la ligne de commande pour que cela fonctionne. Je recherche une meilleure façon de ne pas avoir à commenter ces lignes lorsque je lance Android Studio.
Sayooj Valsan
@Bobrovsky: fonctionne-t-il sous Windows?. Devrions-nous mentionner tout cela dans les environnements système?
DKV
12

Il est possible de prendre n'importe quel projet de gradle Android Studio existant et de le construire / signer à partir de la ligne de commande sans modifier aucun fichier. Cela le rend très agréable pour stocker votre projet dans le contrôle de version tout en gardant vos clés et mots de passe séparés et non dans votre fichier 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
la source
9

La réponse acceptée utilise un fichier pour contrôler le fichier de clés à utiliser pour signer l'APK qui réside dans le même dossier racine du projet. Lorsque nous utilisons des vcs comme Git , cela pourrait être une mauvaise chose lorsque nous oublions d'ajouter le fichier de propriétés à la liste des ignorés. Parce que nous divulguerons notre mot de passe au monde. Les problèmes persistent.

Au lieu de créer le fichier de propriétés dans le même répertoire dans notre projet, nous devrions le créer à l'extérieur. Nous le faisons à l'extérieur en utilisant le fichier gradle.properties.

Voici les étapes:

1.Modifiez ou créez gradle.properties sur votre projet racine et ajoutez le code suivant, n'oubliez pas de modifier le chemin avec le vôtre:

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

2.Créez androidproject.properties dans / votre / chemin / et ajoutez-y le code suivant, n'oubliez pas de changer /your/path/to/android.keystore en chemin de votre keystore:

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

3.Dans votre module d'application build.gradle (pas la racine de votre projet build.gradle), ajoutez le code suivant s'il n'existe pas ou ajustez-le:

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

4.Ajoutez le code suivant sous le code à l'étape 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  
     }  
   }  

Ce code recherchera la propriété AndroidProject.signing dans gradle.properties à partir de l' étape 1 . Si la propriété est trouvée, elle traduira la valeur de la propriété en chemin de fichier pointant vers androidproject.properties que nous créons à l' étape 2 . Ensuite, toute la valeur de propriété de celle-ci sera utilisée comme configuration de signature pour notre build.gradle.

Maintenant, nous n'avons plus à nous inquiéter du risque d'exposer le mot de passe de notre keystore.

En savoir plus sur Signature de l'apk Android sans mettre les informations du keystore dans build.gradle

ישו אוהב אותך
la source
Cela fonctionne bien pour moi. juste pour savoir pourquoi ils utilisent le fichier storeFile (System.getenv ("KEYSTORE"))
DKV
9

Pour ceux qui cherchent à mettre leurs informations d'identification dans un fichier JSON externe et à le lire à partir du gradle, voici ce que j'ai fait:

mon_projet / credentials.json:

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

mon_projet / 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 raison pour laquelle j'ai choisi un .jsontype de fichier, et non un .propertiestype de fichier (comme dans la réponse acceptée), est que je voulais également stocker d'autres données (d'autres propriétés personnalisées dont j'avais besoin) dans ce même fichier ( my_project/credentials.json), tout en continuant à analyser progressivement le signer également des informations à partir de ce fichier.

SudoPlz
la source
Cela me semble être la meilleure solution.
Aspiring Dev
4

Cette question a reçu de nombreuses réponses valides, mais je voulais partager mon code qui peut être utile pour les responsables de la bibliothèque , car il laisse l'original build.gradleassez propre .

J'ajoute un dossier au répertoire du module que je gitignore. Cela ressemble à ceci:

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

keystore.jkset signing.propertiesdevrait être explicite. Et signing.gradleressemble à ceci:

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]

Et l'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 ...
}

Comme vous pouvez le voir, vous n'avez pas du tout à spécifier les buildTypes, si l'utilisateur a accès à un signingrépertoire valide , il le met simplement dans le module et il peut créer une application de version signée valide, sinon cela fonctionne juste pour lui comme ce serait normalement le cas.

Michał K
la source
J'aime vraiment cette solution. Notez cependant, apply fromdevrait venir après le androidbloc
mgray88
0

Vous pouvez demander des mots de passe à partir de la ligne de commande:

...

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
  }

  ...
}

...

Cette réponse est apparue précédemment: https://stackoverflow.com/a/33765572/3664487

utilisateur2768
la source
Bien que ce lien puisse répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien pour référence. Les réponses aux liens uniquement peuvent devenir invalides si la page liée change. - De l'avis
mkobit
1
@mkobit, ceci est un lien vers du contenu sur Stack Overflow! Je pourrais, bien sûr, copier et coller le contenu lié, mais cela conduit à un contenu dupliqué. Ainsi, je suppose, et me corrige si je me trompe, poster un lien est la meilleure solution. Tout argument selon lequel "la page liée change" doit être rejeté, car le contenu ici pourrait également changer. Je recommande fortement de supprimer votre solution! Parce que le contenu lié offre une excellente solution.
user2768
Eh bien, je pense que le problème est qu'il s'agit toujours d'une réponse "lien uniquement". Je pense que la solution est de l'afficher sous forme de commentaire, de signaler la question comme un double ou d'écrire ici une nouvelle réponse qui résout le problème.
mkobit
Les réponses «avec lien uniquement» devraient être encouragées dans certains cas. Néanmoins, j'ai suivi vos conseils et dupliqué le contenu. (Le contenu dupliqué est clairement problématique, car certains contenus peuvent être mis à jour, alors que le contenu restant peut ne pas l'être.)
user2768
En effet, je viens de mettre à jour la réponse et le contenu dupliqué pose des problèmes! S'il existe une politique contre les réponses par lien uniquement, elle doit être adaptée pour tenir compte de ces cas secondaires.
user2768
0

Mon mot de passe contenait un caractère spécial dont le signe dollar $ et je devais l'échapper dans le fichier gradle.properties. Après cela, la signature a fonctionné pour moi.

Yogendra Ghatpande
la source