Unterschreiben Sie APK, ohne die Keystore-Informationen in build.gradle einzufügen


152

Ich versuche, den Signaturprozess so einzurichten, dass das Schlüsselspeicherkennwort und das Schlüsselkennwort nicht in der Projektdatei build.gradlegespeichert werden.

Derzeit habe ich folgendes in build.gradle:

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

    buildTypes {
        release {
            signingConfig signingConfigs.release            
        }
    }
}

Es funktioniert einwandfrei, aber ich darf die Werte für storePasswordund keyPasswordin mein Repository nicht einfügen. Ich würde es vorziehen, nicht storeFileund keyAliasdort auch nicht zu setzen .

Gibt es eine Möglichkeit, das build.gradleso zu ändern , dass Kennwörter von einer externen Quelle abgerufen werden (z. B. eine Datei, die sich nur auf meinem Computer befindet)?

Und natürlich build.gradlesollte die Änderung auf jedem anderen Computer verwendbar sein (auch wenn der Computer keinen Zugriff auf Passwörter hat).

Ich verwende Android Studio und Mac OS X Maverics, wenn es wichtig ist.


"Und natürlich sollte das geänderte build.gradle auf jedem anderen Computer verwendet werden können (auch wenn der Computer keinen Zugriff auf Kennwörter hat)" - wenn die Daten nicht vorhanden build.gradlesind, müssen Sie etwas anderes haben als build.gradle, ob Dies ist eine Anpassung an Umgebungsvariablen (pro Antwort), einer Eigenschaftendatei (pro Antwort) oder auf andere Weise. Wenn Sie nicht bereit sind, Dinge außerhalb von zu haben build.gradle, müssen per Definition alle Signaturinformationen innerhalb sein buid.gradle .
CommonsWare

2
@ CommonsWare Du hast recht. Ich habe jedoch nicht gesagt, dass ich etwas ausschließlich im build.gradle haben möchte. Und ich habe gesagt, dass build.gradle Passwörter von einer externen Quelle erhalten kann (wie eine Datei, die sich nur auf meinem Computer befindet
Bobrovsky


Antworten:


120

Das Schöne an Groovy ist, dass Sie Java-Code frei mischen können und es ziemlich einfach ist, eine Schlüssel- / Wertedatei mit zu lesen java.util.Properties. Vielleicht gibt es eine noch einfachere Möglichkeit, idiomatisches Groovy zu verwenden, aber Java ist immer noch ziemlich einfach.

Erstellen Sie eine keystore.propertiesDatei (in diesem Beispiel im Stammverzeichnis Ihres Projekts neben settings.gradle, können Sie sie jedoch beliebig ablegen:

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

Fügen Sie dies zu Ihrem hinzu 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
Ich musste die Anführungszeichen aus meinem keystore.properties entfernen
Jacob Tabak

6
Es generiert nicht die signierte Version für mich mit Plugin Version 0.9. +. Was soll ich mit dem SigningConfigs-Block und dem buildTypes.release.signingConfig-Element tun? entferne sie?
Fernando Gallego

1
Es scheint, dass das Setzen von storeFile auf einen gültigen Wert (z. B. storeFile file('AndroidManifest.xml')) und das spätere Überschreiben dazu führt, dass der Signaturprozess stattfindet.
miracle2k

5
Das Erstellen führt zu einem Fehler, Error:(24, 0) Could not find property 'android' on root project 'RootProjectName'bei dem Zeile 24 diejenige mit dem if-Block ist. Durch Hinzufügen apply plugin: 'com.android.application'zum Root-Build.g.gle schlägt der Build ebenfalls fehl. Was mache ich falsch?
PhilLab

2
Dies funktioniert nicht im Jahr 2018. Dies muss veraltet sein? Wurde Fehler geworfen, dassCould not get unknown property 'android' for root project
Neon Warge

106

Wenn Sie die Antwort von Scott Barta auf eine Weise anwenden möchten, die dem automatisch generierten Gradle-Code ähnlicher ist, können Sie alternativ eine keystore.propertiesDatei in Ihrem Projektstammordner erstellen :

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

und ändern Sie Ihren Gradle-Code zu:

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

    ...

}

Sie können diese Eigenschaftendatei im Stammverzeichnis Ihres Moduls speichern. In diesem Fall können Sie sie einfach weglassen rootProject. Sie können diesen Code auch so ändern, dass mehrere Eigenschaftensätze für verschiedene Schlüsselspeicher und Schlüsselaliasnamen vorhanden sind.


7
Funktioniert super. Ich habe immer if ( keystorePropertiesFile.exists() )sichergestellt, dass die Datei vorhanden ist, bevor ich versucht habe, die Attribute abzurufen und zu signieren.
Joshua Pinter

Und vergessen Sie nicht, .txtam Ende der keystore.propertiesDatei eine Erweiterung hinzuzufügen .
Levon Petrosyan

11
Sie sollten keine .txtErweiterung für die keystore.propertiesDatei benötigen .
Matt Zukowski

2
Diese Informationen wurden anscheinend hier hinzugefügt - developer.android.com/studio/publish/…
Vadim Kotov

36

Am einfachsten ist es, eine ~/.gradle/gradle.propertiesDatei zu erstellen .

ANDROID_STORE_PASSWORD=hunter2
ANDROID_KEY_PASSWORD=hunter2

Dann kann Ihre build.gradleDatei folgendermaßen aussehen:

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

1
Sollte ich ~ / .gradle / gradle.properties gitignore?
Vzhen

Die vollständigen Anweisungen finden Sie auch in der nativen Dokumentation.
Pencilcheck

23

Nach dem Lesen einiger Links:

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

Da Sie Mac OSX verwenden, können Sie Ihre Kennwörter über den Schlüsselbundzugriff speichern.

So fügen Sie ein Kennwort in Keychain Access hinzu

Dann in Ihren Gradle-Skripten:

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

Verwenden Sie wie folgt:

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
Obwohl Ihre Antwort nur für Mac OSX gilt, gefällt sie mir sehr gut! Beachten Sie, dass der zweite von Ihnen bereitgestellte Link eine Sicherungslösung für andere Plattformen enthält, falls jemand die Unterstützung für mehrere Plattformen implementieren muss.
Delblanco

Können Sie bitte die gleiche Lösung auch für Linux und Windows anbieten? Vielen Dank.
Jay Mungara

18

So mache ich es. Umgebungsvariablen verwenden

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

3
Leider müssen dafür für jedes Projekt auf jedem Computer Systemumgebungen erstellt werden . Ansonsten bekomme ich folgenden FehlerNeither path nor baseDir may be null or empty string. path='null'
Bobrovsky

@ Bobrovsky Ich weiß, dass diese Frage beantwortet wird, aber Sie können Systemumgebungsvariablen oder die Datei gradle.properties verwenden. Sie möchten wahrscheinlich die Datei gradle.properties verwenden. Sie können es für mehrere Projekte verwenden.
Jared Burrows

3
Dies funktioniert unter MacOSX nur, wenn Sie Android Studio über die Befehlszeile ausführen.
Henrique de Sousa

Ich stimme allen oben genannten Punkten zu. Ich habe die gleiche Konfiguration und Sie können diese nicht in Android Studio kompilieren. Sie müssen über die Befehlszeile ausgeführt werden, damit dies funktioniert. Ich suche nach einem besseren Weg, damit ich diese Zeilen nicht kommentieren muss, wenn ich in Android Studio laufe.
Sayooj Valsan

@ Bobrovsky: Funktioniert es unter Windows?. Sollten wir all dies in Systemumgebungen erwähnen?
DKV

12

Es ist möglich, ein vorhandenes Android Studio-Gradle-Projekt über die Befehlszeile zu erstellen / zu signieren, ohne Dateien zu bearbeiten. Dies macht es sehr schön, Ihr Projekt in der Versionskontrolle zu speichern, während Sie Ihre Schlüssel und Kennwörter getrennt und nicht in Ihrer build.gradle-Datei aufbewahren:

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

Die akzeptierte Antwort verwendet eine Datei, um zu steuern, welcher Schlüsselspeicher zum Signieren der APK verwendet werden soll, die sich im selben Stammordner des Projekts befindet. Wenn wir VCS wie Git verwenden , kann dies eine schlechte Sache sein, wenn wir vergessen, die Eigenschaftendatei hinzuzufügen, um die Liste zu ignorieren. Weil wir unser Passwort der Welt mitteilen werden. Die Probleme bestehen weiterhin.

Anstatt die Eigenschaftendatei in demselben Verzeichnis innerhalb unseres Projekts zu erstellen, sollten wir sie außerhalb erstellen. Wir schaffen es nach draußen, indem wir die Datei gradle.properties verwenden.

Hier die Schritte:

1.Bearbeiten oder erstellen Sie gradle.properties in Ihrem Stammprojekt und fügen Sie den folgenden Code hinzu. Denken Sie daran, den Pfad mit Ihrem eigenen zu bearbeiten:

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

2.Erstellen Sie androidproject.properties in / your / path / und fügen Sie den folgenden Code hinzu. Vergessen Sie nicht, /your/path/to/android.keystore in Ihren Keystore-Pfad zu ändern:

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

3. Fügen Sie in Ihrem App-Modul build.gradle (nicht in Ihrem Projekt root build.gradle) den folgenden Code hinzu, falls nicht vorhanden, oder passen Sie ihn an:

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

4. Fügen Sie den folgenden Code unter dem Code in Schritt 3 hinzu:

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

Dieser Code sucht ab Schritt 1 in gradle.properties nach der Eigenschaft AndroidProject.signing . Wenn die Eigenschaft gefunden wird, wird der Eigenschaftswert als Dateipfad übersetzt, der auf androidproject.properties verweist, die wir in Schritt 2 erstellt haben . Dann wird der gesamte Eigenschaftswert daraus als Signaturkonfiguration für unser build.gradle verwendet.

Jetzt müssen wir uns nicht mehr um das Risiko kümmern, unser Keystore-Passwort preiszugeben.

Lesen Sie mehr bei Signing Android apk, ohne Keystore-Informationen in build.gradle zu setzen


Das funktioniert gut für mich. Nur um zu wissen, warum sie die storeFile-Datei (System.getenv ("KEYSTORE")) verwenden
DKV

9

Für diejenigen, die ihre Anmeldeinformationen in eine externe JSON-Datei einfügen und aus dem Gradle lesen möchten, habe ich Folgendes getan:

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

Der Grund, warum ich einen .jsonDateityp und keinen .propertiesDateityp gewählt habe (wie in der akzeptierten Antwort), ist, dass ich auch andere Daten (andere benutzerdefinierte Eigenschaften, die ich benötigte) in derselben Datei speichern wollte ( my_project/credentials.json) und trotzdem gradle parse the Signieren von Informationen auch aus dieser Datei.


Scheint mir die beste Lösung zu sein.
Aspiring Dev

4

Diese Frage hat viele gültige Antworten erhalten, aber ich wollte meinen Code teilen, was für Bibliotheksverwalter nützlich sein kann , da das Original dadurch build.gradleziemlich sauber bleibt .

Ich füge dem Modulverzeichnis einen Ordner hinzu, den ich gitignore. Es sieht aus wie das:

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

keystore.jksund signing.propertiessollte selbsterklärend sein. Und signing.gradlesieht so aus:

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]

Und das 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 ...
}

Wie Sie sehen, müssen Sie die buildTypes überhaupt nicht angeben. Wenn der Benutzer Zugriff auf ein gültiges signingVerzeichnis hat, legt er es einfach in das Modul und kann eine gültige signierte Release-Anwendung erstellen, andernfalls funktioniert es nur für ihn es würde normalerweise tun.


Diese Lösung gefällt mir sehr gut. Beachten Sie jedoch, apply fromsollte nach dem androidBlock kommen
mgray88

0

Sie können Kennwörter über die Befehlszeile anfordern:

...

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
  }

  ...
}

...

Diese Antwort wurde zuvor angezeigt: https://stackoverflow.com/a/33765572/3664487


Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verknüpfte Seite ändert. - Von der Überprüfung
mkobit

1
@mkobit, dies ist ein Link zu Inhalten auf Stack Overflow! Ich könnte den verlinkten Inhalt natürlich kopieren und einfügen, aber das führt zu doppelten Inhalten. Daher gehe ich davon aus und korrigiere mich, wenn ich falsch liege. Das Posten eines Links ist die beste Lösung. Jedes Argument, dass "die verlinkte Seite sich ändert", sollte zurückgewiesen werden, da sich auch der Inhalt hier ändern könnte. Ich empfehle dringend gegen Ihre Lösung zu löschen! Weil der verlinkte Inhalt eine hervorragende Lösung bietet.
user2768

Nun, ich denke, das Problem ist, dass dies immer noch eine "Nur-Link" -Antwort ist. Ich denke, die Lösung besteht darin, sie als Kommentar zu veröffentlichen, die Frage als Duplikat zu kennzeichnen oder hier eine neue Antwort zu schreiben, die das Problem behebt.
Mkobit

In einigen Fällen sollten "Nur-Link" -Antworten empfohlen werden. Trotzdem habe ich Ihren Rat befolgt und Inhalte dupliziert. (Doppelter Inhalt ist eindeutig problematisch, da einige Inhalte möglicherweise aktualisiert werden, während der verbleibende Inhalt möglicherweise nicht aktualisiert wird.)
user2768 18.11.15

In der Tat habe ich gerade die Antwort aktualisiert und doppelte Inhalte verursachen Probleme! Wenn es eine Richtlinie gegen Nur-Link-Antworten gibt, sollte diese angepasst werden, um solche Eckfälle zu berücksichtigen.
user2768

0

Mein Passwort enthielt ein Sonderzeichen, das Dollarzeichen $ und ich musste es in der Datei gradle.properties umgehen. Danach hat das Signieren für mich funktioniert.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.