Підпишіть APK, не додаючи інформацію про сховище ключів у build.gradle


152

Я намагаюся налаштувати процес підписання так, щоб пароль зберігання ключів і пароль ключа не зберігалися у build.gradleфайлі проекту .

На даний момент у мене є build.gradle:

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

    buildTypes {
        release {
            signingConfig signingConfigs.release            
        }
    }
}

Це прекрасно працює, але я не повинен ставити значення для storePasswordі keyPasswordв моєму сховищі. Я б волів не ставити storeFileі keyAliasтам.

Чи є спосіб змінити build.gradleтак, щоб він отримував паролі від якогось зовнішнього джерела (наприклад, файл, який знаходиться на моєму комп'ютері)?

І звичайно, змінені build.gradleповинні бути використані на будь-якому іншому комп'ютері (навіть якщо комп'ютер не має доступу до паролів).

Я використовую Android Studio і в Mac OS X Maverics, якщо це має значення.


"І звичайно, змінений build.gradle має бути використаний на будь-якому іншому комп'ютері (навіть якщо комп'ютер не має доступу до паролів)" - якщо дані відсутні build.gradle, вам доведеться мати щось інше, ніж build.gradle, чи це коригування змінних оточуючих середовищ (на одну відповідь), файлу властивостей (за іншою відповіддю) або якимось іншим способом. Якщо ви не бажаєте мати речі поза build.gradle, то, за визначенням, вся інформація про підписи повинна бути всередині buid.gradle .
CommonsWare

2
@CommonsWare Ви маєте рацію. Я не сказав, що хочу мати щось строго в build.gradle. І я сказав, що build.gradle може отримати паролі з якогось зовнішнього джерела (наприклад, файл, який знаходиться лише на моєму комп’ютері
Бобровський


Відповіді:


120

Приємна річ про Groovy полягає в тому, що ви можете вільно змішувати код Java, і це досить легко читати у файлі ключ / значення, використовуючи java.util.Properties. Можливо, є ще простіший спосіб використання ідіоматичного Groovy, але Java все ще досить проста.

Створіть keystore.propertiesфайл (у цьому прикладі в кореневому каталозі вашого проекту поруч settings.gradle, хоча ви можете розмістити його куди завгодно:

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

Додайте це до свого 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
Мені довелося видалити цитати з мого keystore.properties
Якоб Табак

6
Він не генерує підписану версію для мене з плагіном версії 0.9. +. Що я повинен робити з блоком підписуванняConfigs та buildTypes.release.signingConfig? видалити їх?
Фернандо Ґаллего

1
Здається, що встановлення storeFile на будь-яке дійсне значення (наприклад storeFile file('AndroidManifest.xml')), а потім його перезапис пізніше призведе до того, що процес підписання відбудеться.
miracle2k

5
Побудова призводить до помилки, у Error:(24, 0) Could not find property 'android' on root project 'RootProjectName'якій рядок 24 є блоком if. Додавання apply plugin: 'com.android.application'до root build.gradle також дозволяє збільшити збір. Що я роблю неправильно?
PhilLab

2
Це не працює в 2018 році. Це повинно бути застарілим? Could not get unknown property 'android' for root project
Тримав

106

Крім того, якщо ви хочете застосувати відповідь Скотта Барта способом, подібним до автоматично створеного коду Gradle, ви можете створити keystore.propertiesфайл у кореневій папці проекту:

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

і змінити код gradle на:

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

    ...

}

Ви можете зберігати цей файл властивостей у корені вашого модуля, у цьому випадку просто опустіть rootProject, а також ви можете змінити цей код, щоб він мав декілька наборів властивостей для різних магазинів ключів та псевдонімів ключів.


7
Чудово працює. Я if ( keystorePropertiesFile.exists() )намагався переконатися, що файл присутній, перш ніж намагатися отримати атрибути та спробувати підписати.
Джошуа Пінтер

І не забудьте додати .txtрозширення в кінці keystore.propertiesфайлу.
Левон Петросян

11
Вам не потрібно .txtрозширення keystore.propertiesфайлу.
Метт Зуковський

2
Схоже, ця інформація була додана тут - developer.android.com/studio/publish/…
Вадим Котов

36

Найпростіший спосіб - створити ~/.gradle/gradle.propertiesфайл.

ANDROID_STORE_PASSWORD=hunter2
ANDROID_KEY_PASSWORD=hunter2

Тоді ваш build.gradleфайл може виглядати так:

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

1
Чи слід gitignore ~ ​​/ .gradle / gradle.properties?
вжень

Повні вказівки також знаходяться в реакції на рідну документацію.
Pencilcheck

23

Прочитавши кілька посилань:

http://blog.macromates.com/2006/keychain-access-from-shell/ http://www.thoughtworks.com/es/insights/blog/signing-open-source-android-apps-without-disclosing- паролі

Оскільки ви використовуєте Mac OSX, ви можете використовувати Keychain Access для зберігання своїх паролів.

Як додати пароль в Keychain Access

Потім у своїх сценаріях 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]
}

Використовуйте так:

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
хоча ваша відповідь стосується лише Mac OSX, мені це дуже подобається! Зауважте, що друге надане вами посилання містить резервне рішення для інших платформ на випадок, якщо комусь потрібно впровадити підтримку багатьох платформ.
Delblanco

Чи можете ви надати те саме рішення для Linux та Windows? Дякую.
Джей Мунгара

18

Ось як я це роблю. Використовуйте змінні середовища

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

3
На жаль, для цього потрібно створити системне середовище для кожного проекту на кожному комп'ютері . В іншому випадку я отримую наступну помилкуNeither path nor baseDir may be null or empty string. path='null'
Бобровський

@Bobrovsky Я знаю, що на це питання відповіли, але ви можете використовувати змінні системного середовища або файл gradle.properties. Напевно, ви хочете використовувати файл gradle.properties. Ви можете використовувати його для декількох проектів.
Джаред Берроуз

3
це не працює на MacOSX, якщо ви не запустите Android Studio з командного рядка.
Анріке де Суса

Я згоден з усім вище. У мене така ж конфігурація, і ви не можете компілювати це в студії Android. Для цього вам потрібно бігти з командного рядка. Я шукаю кращого способу, так що мені не потрібно коментувати ці рядки під час запуску в Android Studio.
Сайой Валсан

@ Бобровський: Це працює на windows ?. Чи варто згадувати все це в системних середовищах?
DKV

12

Можна взяти будь-який існуючий проект студії Android Studio і створити / підписати його з командного рядка без редагування файлів. Це робить дуже приємним зберігання вашого проекту в контролі версій, зберігаючи ваші ключі та паролі окремо, а не у файлі 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

9

Прийнята відповідь використовує файл, щоб контролювати, який магазин зберігання ключів використовувати для підпису APK, який знаходиться в тій же кореневій папці проекту. Коли ми використовуємо vcs типу Git , це може бути погано, коли ми забудемо додати файл властивостей до списку ігнорування. Тому що ми розкриємо свій пароль всьому світу. Проблеми все ще зберігаються.

Замість того, щоб створити файл властивостей у одному каталозі в рамках нашого проекту, ми повинні зробити його поза Ми робимо це назовні, використовуючи файл gradle.properties.

Ось етапи:

1.Редагуйте або створіть gradle.properties у вашому кореневому проекті та додайте наступний код, не забудьте відредагувати шлях своїм власним:

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

2.Створіть androidproject.properties у / вашому / шляху / та додайте до нього такий код, не забудьте змінити /your/path/to/android.keystore у свій шлях до магазину:

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

3.У модуль додатка build.gradle (не у вашому проекті root build.gradle) додайте наступний код, якщо його немає, або налаштуйте його:

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

4.Додайте наступний код під кодом на кроці 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  
     }  
   }  

Цей код буде шукати властивість AndroidProject.signing у gradle.properties з кроку 1 . Якщо властивість знайдена, вона переведе значення властивості як шлях до файлу, який вказує на androidproject.properties, який ми створюємо на кроці 2 . Тоді все значення властивості з нього буде використовуватися як конфігурація підпису для нашого build.gradle.

Тепер нам більше не потрібно турбуватися про ризик викрити пароль для зберігання ключів.

Детальніше читайте на сторінці Підписання Android apk, не додаючи інформацію про сховище ключів у build.gradle


Це добре працює для мене. просто знати, чому вони використовують файл storeFile (System.getenv ("KEYSTORE"))
DKV

9

Для тих, хто хоче поставити свої облікові дані у зовнішній файл JSON і прочитати, що з gradle це я зробив:

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

Причиною того, що я обрав .jsonтип файлу, а не .propertiesтип файлу (як у прийнятій відповіді), є те, що я хотів також зберігати інші дані (інші спеціальні властивості, які мені потрібні) до того самого файлу ( my_project/credentials.json), і все ще мати розбір gradle також підписувати інформацію з цього файлу.


Здається, найкраще рішення для мене.
Прагнув Дев

4

На це запитання було отримано багато вагомих відповідей, але я хотів поділитися своїм кодом, який може бути корисним для бібліотечних користувачів , оскільки він залишає оригіналом build.gradleдосить чистим .

Я додаю папку в каталог модулів, яку я gitignore. Це виглядає приблизно так:

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

keystore.jksі signing.propertiesмає бути пояснювальним. І signing.gradleвиглядає так:

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]

І оригінал 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 ...
}

Як бачите, вам зовсім не потрібно вказувати buildTypes, якщо користувач має доступ до дійсної signingкаталоги, він просто поміщає його в модуль і він може створити дійсну підписану програму випуску, інакше це просто працює для нього, як зазвичай це робиться.


Мені дуже подобається таке рішення. Зауважте, apply fromслід прийти після androidблоку
mgray88

0

Ви можете запитувати паролі з командного рядка:

...

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
  }

  ...
}

...

Ця відповідь раніше з’явилася: https://stackoverflow.com/a/33765572/3664487


Хоча це посилання може відповісти на питання, краще включити сюди суттєві частини відповіді та надати посилання для довідки. Відповіді лише на посилання можуть стати недійсними, якщо пов’язана сторінка зміниться. - З огляду
mkobit

1
@mkobit, це посилання на вміст у стеку Overflow! Я, звичайно, міг би скопіювати і вставити пов'язаний вміст, але це призводить до дублювання вмісту. Таким чином, я припускаю, і виправте мене, якщо я помиляюся, розміщення посилання є найкращим рішенням. Будь-який аргумент про те, що "пов’язана сторінка змінюється", слід відхилити, виходячи з того, що зміст тут також може змінитися. Я настійно рекомендую проти вашого рішення видалити! Оскільки пов'язаний вміст забезпечує чудове рішення.
користувач2768

Ну, я думаю, проблема полягає в тому, що це все ще відповідь "лише для посилань". Я думаю, що рішення полягає в тому, щоб розмістити його як коментар, позначити це питання як дублікат або написати тут нову відповідь, яка вирішує проблему.
mkobit

У деяких випадках слід заохочувати відповіді "лише для посилань". Тим не менш, я дотримувався ваших порад і продублював вміст. (Дубльований контент явно проблематичний, тому що деякий вміст може бути оновлений, тоді як інший вміст може бути не.)
user2768

Дійсно, я щойно оновив відповідь, і дублюючий вміст викликає проблеми! Якщо існує політика проти відповідей, що стосуються лише посилань, її слід адаптувати для розгляду таких кутових випадків.
user2768

0

Мій пароль містив спеціальний символ, який підписує долар $, і мені довелося уникати цього у файлі gradle.properties. Після цього підписання працювало на мене.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.