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

CurlyCorvus
источник
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 подписывая информацию из этого файла, а также.

SudoPlz
источник
Похоже, лучшее решение для меня.
Стремящийся 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

user2768
источник
Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если связанная страница изменится. - Из обзора
mkobit
1
@mkobit, это ссылка на контент в Stack Overflow! Я мог бы, конечно, копировать и вставлять связанный контент, но это приводит к дублированию контента. Таким образом, я предполагаю и исправляю меня, если я ошибаюсь, размещение ссылки является лучшим решением. Любой аргумент о том, что «связанная страница изменяется», должен быть отклонен на том основании, что содержание здесь также может измениться. Я настоятельно рекомендую против вашего решения удалить! Потому что связанный контент предоставляет отличное решение.
user2768 18.11.15
Ну, я думаю, проблема в том, что это все еще ответ "только для ссылок". Я думаю, что решение состоит в том, чтобы опубликовать его в качестве комментария, пометить вопрос как дубликат или написать новый ответ, который решает проблему.
mkobit
В некоторых случаях следует поощрять ответы только для ссылок. Тем не менее, я следовал вашим советам и дублировал контент. (Дублированный контент явно проблематичен, потому что некоторый контент может быть обновлен, тогда как остальной контент может не
обновляться
Действительно, я только что обновил ответ, и дублированный контент вызывает проблемы! Если существует политика в отношении ответов, содержащих только ссылки, то она должна быть адаптирована для рассмотрения таких крайних случаев.
user2768 24.11.15
0

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

Йогендра Гатпанде
источник