Podpisz APK bez umieszczania informacji o magazynie kluczy w build.gradle

152

Próbuję skonfigurować proces podpisywania, aby hasło do magazynu kluczy i hasło klucza nie były przechowywane w build.gradlepliku projektu .

Obecnie w build.gradle:

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

    buildTypes {
        release {
            signingConfig signingConfigs.release            
        }
    }
}

Działa doskonale, ale nie mogę umieszczać wartości storePasswordi keyPasswordw moim repozytorium. Wolałbym nie stawiać storeFilei keyAliastam też.

Czy istnieje sposób, aby to zmienić, build.gradletak aby uzyskiwał hasła z jakiegoś zewnętrznego źródła (np. Plik znajdujący się tylko na moim komputerze)?

Oczywiście zmieniony build.gradlepowinien być użyteczny na każdym innym komputerze (nawet jeśli komputer nie ma dostępu do haseł).

Używam Android Studio i Mac OS X Maverics, jeśli ma to znaczenie.

Bobrovsky
źródło
„I oczywiście, zmieniony build.gradle powinien być użyteczny na każdym innym komputerze (nawet wtedy, gdy komputer nie ma dostępu do haseł)” - jeśli dane nie jest build.gradle, trzeba mieć coś innego niż build.gradle, czy to jest dostosowanie do zmiennych środowiskowych (na jedną odpowiedź), plik właściwości (na inną odpowiedź) lub w inny sposób. Jeśli nie chcesz mieć rzeczy na zewnątrz build.gradle, z definicji wszystkie informacje dotyczące podpisu muszą znajdować się wewnątrz buid.gradle .
CommonsWare
2
@CommonsWare Masz rację. Nie powiedziałem jednak, że chcę mieć coś ściśle w ramach build.gradle. Powiedziałem też, że build.gradle może uzyskać hasła z jakiegoś zewnętrznego źródła (jak plik, który znajduje się tylko na moim komputerze
Bobrovsky

Odpowiedzi:

120

Zaletą Groovy jest to, że możesz dowolnie mieszać kod Java i jest dość łatwy do odczytania w pliku klucz / wartość za pomocą java.util.Properties. Być może istnieje jeszcze łatwiejszy sposób korzystania z idiomatycznego Groovy, ale Java jest nadal dość prosta.

Utwórz keystore.propertiesplik (w tym przykładzie w katalogu głównym projektu obok settings.gradle, ale możesz go umieścić w dowolnym miejscu:

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

Dodaj to do 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
źródło
29
Musiałem usunąć cytaty z mojego magazynu kluczy.properties
Jacob Tabak
6
Nie generuje podpisanej wersji dla mnie z wtyczką w wersji 0.9. +. Co mam zrobić z blokiem signingConfigs i elementem buildTypes.release.signingConfig? Usuń ich?
Fernando Gallego
1
Wydaje się, że ustawienie storeFile na dowolną prawidłową wartość (np. storeFile file('AndroidManifest.xml')), A następnie późniejsze nadpisanie powoduje, że ma miejsce proces podpisywania.
miracle2k
5
Budowanie powoduje błąd, w Error:(24, 0) Could not find property 'android' on root project 'RootProjectName'którym wiersz 24 jest linią z blokiem if. Dodanie apply plugin: 'com.android.application'do katalogu głównego build.gradle umożliwia również niepowodzenie kompilacji. Co ja robię źle?
PhilLab
2
To nie działa w roku 2018. To musi zostać wycofane? Could not get unknown property 'android' for root project
Zachował
106

Alternatywnie, jeśli chcesz zastosować odpowiedź Scotta Barty w sposób bardziej podobny do automatycznie wygenerowanego kodu gradle, możesz utworzyć keystore.propertiesplik w folderze głównym projektu:

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

i zmodyfikuj swój kod gradle, aby:

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

    ...

}

Możesz przechowywać ten plik właściwości w katalogu głównym swojego modułu, w takim przypadku po prostu go pomiń rootProject, a także możesz zmodyfikować ten kod, aby mieć kilka zestawów właściwości dla różnych magazynów kluczy i aliasów kluczy.

CurlyCorvus
źródło
7
Działa świetnie. Kiedyś if ( keystorePropertiesFile.exists() )upewniłem się, że plik jest obecny, zanim spróbowałem uzyskać atrybuty i spróbowałem podpisać.
Joshua Pinter
I nie zapomnij dodać .txtrozszerzenia na końcu keystore.propertiespliku.
Levon Petrosyan,
11
Nie powinieneś potrzebować .txtrozszerzenia w keystore.propertiespliku.
Matt Zukowski
2
Wygląda na to, że ta informacja została tutaj dodana - developer.android.com/studio/publish/ ...
Vadim Kotov
36

Najłatwiej jest stworzyć ~/.gradle/gradle.propertiesplik.

ANDROID_STORE_PASSWORD=hunter2
ANDROID_KEY_PASSWORD=hunter2

Wtedy twój build.gradleplik może wyglądać tak:

android {
    signingConfigs {
        release {
            storeFile file('yourfile.keystore')
            storePassword ANDROID_STORE_PASSWORD
            keyAlias 'youralias'
            keyPassword ANDROID_KEY_PASSWORD
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}
Dan Fabulich
źródło
1
Czy powinienem gitignore ~ ​​/ .gradle / gradle.properties?
vzhen,
Pełna instrukcja znajduje się również w natywnej dokumentacji Reag.
Pencilcheck
23

Po przeczytaniu kilku linków:

http://blog.macromates.com/2006/keychain-access-from-shell/ http://www.hardtworks.com/es/insights/blog/signing-open-source-android-apps-without-disclosing- Hasła

Ponieważ używasz systemu Mac OSX, możesz użyć Dostępu do pęku kluczy do przechowywania haseł.

Jak dodać hasło w dostępie do pęku kluczy

Następnie w swoich skryptach ocen:

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

Użyj w ten sposób:

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
źródło
2
chociaż twoja odpowiedź dotyczy tylko Mac OSX, bardzo mi się podoba! Zwróć uwagę, że drugie łącze, które podałeś, zawiera rozwiązanie do tworzenia kopii zapasowych dla innych platform, na wypadek gdyby ktoś musiał wdrożyć obsługę wielu platform.
Delblanco
Czy możesz zapewnić to samo rozwiązanie również dla systemu Linux i Windows? Dzięki.
Jay Mungara,
18

Tak to robię. Użyj zmiennych środowiskowych

  signingConfigs {
    release {
        storeFile file(System.getenv("KEYSTORE"))
        storePassword System.getenv("KEYSTORE_PASSWORD")
        keyAlias System.getenv("KEY_ALIAS")
        keyPassword System.getenv("KEY_PASSWORD")
    }
Madhur Ahuja
źródło
3
Niestety wymaga to tworzenia środowisk systemowych dla każdego projektu na każdym komputerze . W przeciwnym razie otrzymuję następujący błądNeither path nor baseDir may be null or empty string. path='null'
Bobrovsky
@Bobrovsky Wiem, że odpowiedź na to pytanie została udzielona, ​​ale możesz użyć systemowych zmiennych środowiskowych lub pliku gradle.properties. Prawdopodobnie chcesz użyć pliku gradle.properties. Możesz go używać do wielu projektów.
Jared Burrows,
3
to nie działa na MacOSX, chyba że uruchomisz Android Studio z wiersza poleceń.
Henrique de Sousa
Zgadzam się z powyższym. Mam taką samą konfigurację i nie możesz tego skompilować w Android Studio. Aby to zadziałało, musisz uruchomić z wiersza poleceń. Szukam lepszego sposobu, aby nie musieć komentować tych wierszy, gdy uruchomię w Android Studio.
Sayooj Valsan
@Bobrovsky: Czy to działa w systemie Windows ?. Czy powinniśmy o tym wszystkim wspomnieć w środowiskach systemowych?
DKV
12

Możliwe jest pobranie dowolnego istniejącego projektu gradle Android Studio i zbudowanie / podpisanie go z wiersza poleceń bez edycji żadnych plików. To sprawia, że ​​bardzo przyjemnie jest przechowywać projekt w kontroli wersji, zachowując klucze i hasła osobno, a nie w pliku 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
źródło
9

Zaakceptowana odpowiedź używa pliku do kontrolowania, którego magazynu kluczy użyć do podpisania pliku APK, który znajduje się w tym samym folderze głównym projektu. Kiedy używamy vcs, takich jak Git , może być źle, gdy zapomnimy dodać plik właściwości do ignorowania listy. Ponieważ ujawnimy światu nasze hasło. Problemy nadal istnieją.

Zamiast tworzyć plik właściwości w tym samym katalogu w naszym projekcie, powinniśmy zrobić to na zewnątrz. Robimy to na zewnątrz za pomocą pliku gradle.properties.

Oto kroki:

1. Edytuj lub utwórz gradle.properties w swoim głównym projekcie i dodaj następujący kod, pamiętaj, aby edytować ścieżkę za pomocą własnej:

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

2.Utwórz androidproject.properties w / your / path / i dodaj do niego następujący kod, nie zapomnij zmienić /your/path/to/android.keystore na ścieżkę do magazynu kluczy:

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

3. W module aplikacji build.gradle (nie w katalogu głównym projektu build.gradle) dodaj następujący kod, jeśli nie istnieje, lub dostosuj go:

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

4. Dodaj następujący kod poniżej kodu w kroku 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  
     }  
   }  

Ten kod wyszuka właściwość AndroidProject.signing w gradle.properties z kroku 1 . Jeśli właściwość zostanie znaleziona, przetłumaczy wartość właściwości jako ścieżkę do pliku, która wskazuje na androidproject.properties, które utworzymy w kroku 2 . Następnie cała wartość właściwości z niego zostanie użyta jako konfiguracja podpisywania dla naszego build.gradle.

Teraz nie musimy ponownie martwić się ryzykiem ujawnienia naszego hasła do magazynu kluczy.

Przeczytaj więcej na temat podpisywania aplikacji na Androida bez umieszczania informacji o magazynie kluczy w pliku build.gradle

ישו אוהב אותך
źródło
To działa dobrze dla mnie. tylko po to, aby wiedzieć, dlaczego używają pliku storeFile (System.getenv ("KEYSTORE"))
DKV,
9

Dla tych, którzy chcą umieścić swoje dane uwierzytelniające w zewnętrznym pliku JSON i przeczytać to z gradle, oto co zrobiłem:

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

Powodem, dla którego wybrałem .jsontyp pliku, a nie .propertiestyp pliku (jak w zaakceptowanej odpowiedzi), jest to, że chciałem również przechowywać inne dane (inne potrzebne właściwości niestandardowe) w tym samym pliku ( my_project/credentials.json) i nadal mieć gradle przeanalizować podpisywanie również informacji z tego pliku.

SudoPlz
źródło
Wydaje mi się, że to najlepsze rozwiązanie.
Aspiring Dev
4

To pytanie otrzymało wiele poprawnych odpowiedzi, ale chciałem udostępnić mój kod, który może być przydatny dla opiekunów bibliotek , ponieważ pozostawia oryginał build.gradlecałkiem czysty .

Dodaję folder do katalogu modułu, który ja gitignore. To wygląda tak:

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

keystore.jksi signing.propertiespowinno być oczywiste. I signing.gradlewygląda tak:

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]

I oryginał 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 ...
}

Jak widać, nie musisz w ogóle określać buildTypes, jeśli użytkownik ma dostęp do prawidłowego signingkatalogu, po prostu umieszcza go w module i może zbudować poprawną podpisaną aplikację do wydania, w przeciwnym razie działa to po prostu dla niego jak normalnie by to zrobił.

Michał K.
źródło
Bardzo podoba mi się to rozwiązanie. Uwaga jednak, apply frompowinien przyjść po androidbloku
mgray88
0

Możesz zażądać hasła z wiersza poleceń:

...

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
  }

  ...
}

...

Ta odpowiedź pojawiła się wcześniej: https://stackoverflow.com/a/33765572/3664487

user2768
źródło
Chociaż ten link może odpowiedzieć na pytanie, lepiej jest zawrzeć tutaj zasadnicze części odpowiedzi i podać link do odniesienia. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie. - Z recenzji
mkobit
1
@mkobit, to jest łącze do treści w witrynie Stack Overflow! Mógłbym oczywiście skopiować i wkleić linkowaną treść, ale prowadzi to do powielania treści. Dlatego zakładam i poprawiam, jeśli się mylę, umieszczenie linku jest najlepszym rozwiązaniem. Każdy argument, że „zmienia się strona, do której prowadzi link”, powinien zostać odrzucony, ponieważ treść również może się zmienić. Zdecydowanie polecam rozwiązanie, aby usunąć! Ponieważ zawartość, do której prowadzi łącze, stanowi doskonałe rozwiązanie.
user2768
Cóż, myślę, że problem polega na tym, że nadal jest to odpowiedź „tylko łącze”. Myślę, że rozwiązaniem jest opublikowanie tego jako komentarza, oflagowanie pytania jako duplikatu lub napisanie tutaj nowej odpowiedzi, która rozwiązuje problem.
mkobit
W niektórych przypadkach należy zachęcać do udzielania odpowiedzi „tylko łącze”. Niemniej jednak postąpiłem zgodnie z twoją radą i zduplikowanymi treściami. (Zduplikowana treść jest ewidentnie problematyczna, ponieważ niektóre treści mogą zostać zaktualizowane, podczas gdy pozostała treść może nie być.)
user2768
Rzeczywiście, właśnie zaktualizowałem odpowiedź i powielanie treści powoduje problemy! Jeśli istnieje zasada dotycząca odpowiedzi obejmujących tylko łącza, należy ją dostosować do takich przypadków narożnych.
user2768
0

Moje hasło zawierało specjalny znak, którym był znak dolara $ i musiałem przed tym uniknąć w pliku gradle.properties. Potem podpisywanie zadziałało dla mnie.

Yogendra Ghatpande
źródło