Подпишите 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 может получить пароли из какого-то внешнего источника (например, файла, который находится только на моем компьютере
— Бобровский


Я помечен как дубликат stackoverflow.com/questions/18328730/… , на основе meta.stackoverflow.com/questions/311044/…
— user2768

Ответы:


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. +. Что я должен делать с блоком signatureConfigs и элементом 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'в корень build.gradle также дает сбой при сборке. Что я делаю не так?
— PhilLab

2
Это не работает в 2018 году. Это должно быть устарело? Could not get unknown property 'android' for root project
— Сохранял

107

В качестве альтернативы, если вы хотите применить ответ Скотта Барты способом, более похожим на автоматически сгенерированный код 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

Тогда в ваших скриптах:

/* 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.
— Сайой Валсан

@ Бобровский: это работает на окнах? Должны ли мы упомянуть все это в системных средах?
— DKV

12

Можно взять любой существующий проект для Android Studio gradle и создать / подписать его из командной строки без редактирования каких-либо файлов. Это делает его очень удобным для хранения вашего проекта в системе контроля версий, при этом ваши ключи и пароли хранятся отдельно, а не в файле 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 / и добавьте в него следующий код, не забудьте изменить /your/path/to/android.keystore на путь вашего хранилища ключей:

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

3.В своем модуле приложения build.gradle (не в корневом каталоге проекта 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.

Теперь нам не нужно снова беспокоиться о риске раскрытия пароля нашего хранилища ключей.

Читайте больше на Подписи apk Android, не помещая информацию о хранилище ключей в 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 / приложение / 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 подписывая информацию из этого файла, а также.


Похоже, лучшее решение для меня.
— Стремящийся Dev

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, это ссылка на контент в Stack Overflow! Я мог бы, конечно, копировать и вставлять связанный контент, но это приводит к дублированию контента. Таким образом, я предполагаю и исправляю меня, если я ошибаюсь, размещение ссылки является лучшим решением. Любой аргумент о том, что «связанная страница изменяется», должен быть отклонен на том основании, что содержание здесь также может измениться. Я настоятельно рекомендую против вашего решения удалить! Потому что связанный контент предоставляет отличное решение.
— user2768 18.11.15

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

В некоторых случаях следует поощрять ответы только для ссылок. Тем не менее, я следовал вашим советам и дублировал контент. (Дублированный контент явно проблематичен, потому что некоторый контент может быть обновлен, тогда как остальной контент может не
— обновляться

Действительно, я только что обновил ответ, и дублированный контент вызывает проблемы! Если существует политика в отношении ответов, содержащих только ссылки, то она должна быть адаптирована для рассмотрения таких крайних случаев.
— user2768 24.11.15

0

Мой пароль содержал специальный символ, который обозначал знак доллара $, и мне пришлось экранировать его в файле gradle.properties. После этого подписание сработало для меня.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.