Signing open source Android apps without disclosing passwords

You’ve created an open source Android app. The source code is freely available to anybody on GitHub; you might even have a few contributors and followers. You’ve set up a keystore and a private key to sign and release your app.

Now, how do you set up app signing in Gradle? And more importantly, how do you minimize the risk of committing the config files containing passwords to GitHub?

Anybody with the keystore and the passwords could release a new version of the app in your name. At the same time it would be self-defeading for an open source project to withhold build configuration from the shared codebase only to minimize that risk.

What we need is a way to separate build configuration from password storage and make it hard for the password to ever get committed.

Compile a release build and sign it, the Gradle way

The documentation provides steps to compile a release build in Eclipse and Ant and details how to sign your app from the command line. No help for Gradle though, despite it being the standard build tool for Android Studio. Here’s how to configure your Gradle project to sign release builds.

Add the following to your android {} block in build.gradle:

android {
  // Other settings

  signingConfigs {
    release
  }

  buildTypes {
    release {
      signingConfig signingConfigs.release
      runProguard false
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
      zipAlign true
    }
  }
}

Note that it disables Proguard on your release build. That should be fine for most open source projects, except if other secrets are included into your code that are not available on the shared codebase (API access keys and the like). To enable Proguard replace line 11 with runProguard true.

Separate build configuration from password storage

Notice we haven’t yet specified any keystore or passwords for the app signing. We want to separate the gradle config from storage of sensitive data. The gradle config will be committed to the shared codebase while the files holding passwords will be kept private. Only a template will be committed publicly.

Add the following somewhere in build.gradle after the android {} block, for example at the very bottom:

def props = new Properties()
props.load(new FileInputStream(rootProject.file("release.properties")))
android.signingConfigs.release.storeFile rootProject.file(props.keyStore)
android.signingConfigs.release.storePassword props.keyStorePassword
android.signingConfigs.release.keyAlias props.keyAlias
android.signingConfigs.release.keyPassword props.keyAliasPassword

This code reads the relevant configuration from an external properties file, namely release.properties in the root folder of your project. To make files relative to the current project folder instead of the root folder, replace rootProject by project in lines 2 and 3.

Before creating this properties file we’ll first add a sample template. It will help other team members set up their own signing by copying the template to the properties file and adjusting the settings. Create a file release.properties.sample with the following content:

keyStore=release.keystore
keyStorePassword=mysecretpassword
keyAlias=myapp-release
keyAliasPassword=mysecretpassword

Now that the sample is in place, let’s copy it to release.properties and fill in the missing details:

  • The keyStore points to the keystore file created by the keytool utility or by other frontends such as the keystore tool inside Android Studio. This file can live outside your source folder, which is generally a good idea though not mandatory.

    Be sure to backup the keystore to a safe location. It cannot be restored. If you lose the keystore you won’t be able to release new versions of your app.

  • The keyAlias is the alias of the private key used to sign your app. You’ve specified this value when you set up the keystore.

  • The keyStorePassword and keyAliasPassword are the passwords for the keystore and the private key, respectively. Again, as specified when you created the keystore.

You should now be able to run the following to generate a release build of your app:

gradle assembleRelease

You will find the signed APK file at myapp/build/apk/myapp-release.apk.

Ignore your local password config

At this point you have the following files in your project:

.
├── build.gradle               # gradle build config
├── myapp/                     # Android app project
├── release.keystore           # keystore to sign release
├── release.properties         # release signing config file
├── release.properties.sample  # sample for `release.properties`
└── [...]                      # miscellaneous other files

In order to minimize the risk of committing sensitive data to the shared codebase we will ignore release.keystore and release.properties from change tracking in our SCM. This operation is tool-dependent. Here are examples for some common SCM tools.

  • Git: add filenames to .gitignore, prepended with / to root them at the current directory.

    touch .gitignore
    echo $'/release.keystore\n/release.properties' >> .gitignore
    
  • Mercurial: .hgignore entries can only be rooted if they are declared as regexp patterns and prepended with ^.

    touch .hgignore
    echo $'syntax: regexp\n^release.keystore\n^release.properties' >> .hgignore
    
  • Subversion: a little more involved because the svn:ignore property cannot simply be set to ignore the two files. Doing this would overwrite the previous property value. Instead entries must be appended to the property.

    svn propset svn:ignore $(svn propget svn:ignore .)$'\nrelease.keystore\nrelease.properties' .
    

After running one of these commands the two sensitive files should no longer appear in the list of changes. The risk of accidentally committing them to a public codebase is reduced. You can now commit the other changes.

Let’s do the Keychain twist (Mac OS X only)

Note that the following only works on Mac OS X, although similar approaches exist for both Linux and Windows.

Mac OS X comes with a facility called the Keychain. The Keychain is a password management system built into the OS and used for a variety of tasks such as storing WiFi passwords, mail account information, TimeMachine backup passwords, etc. It conveniently provides a command line interface. We will leverage this capability by storing the passwords in the Keychain and securely accessing them when signing the app.

Replace the previously added code snippet (the one that reads the sensitive properties file) with the following:

ext.props = new Properties()
props.load(new FileInputStream(rootProject.file("release.properties")))
android.signingConfigs.release.storeFile rootProject.file(props.keyStore)
android.signingConfigs.release.keyAlias props.keyAlias
android.signingConfigs.release.storePassword fetchPassword('keyStorePassword')
android.signingConfigs.release.keyPassword fetchPassword('keyAliasPassword')

import org.apache.tools.ant.taskdefs.condition.Os

def fetchPassword(identifier) {
    if (!Os.isFamily(Os.FAMILY_MAC)) {
        return props.getProperty(identifier)
    }

    def keychainLabel = "release-${identifier}"
    println "Fetching keychain password with account '${name}' and label '${keychainLabel}"

    def stdout = new ByteArrayOutputStream()
    def stderr = new ByteArrayOutputStream()
    def keychainExec = exec {
        commandLine 'security', '-q', 'find-generic-password', '-a', name, '-gl', "${keychainLabel}"
        standardOutput = stdout
        errorOutput = stderr
        ignoreExitValue true
    }

    if (keychainExec.exitValue != 0) {
        println stdout.toString()
        println stderr.toString()
        return
    }

    (stderr.toString().trim() =~ /password: "(.*)"/)[0][1]
}

This code will work on any platform. On Mac OS X it reads the password from the Keychain. On other platforms it still reads the passwords from the properties file. You can use this snippet even if there is a mix of platforms on your team.

Now that the passwords will be provided by the Keychain we can get rid of the passwords in the properties file. Accidentally sharing the properties file will no longer expose the passwords.

keyStore=release.keystore
keyAlias=myapp-release

Note that it still is best practise not to commit this file to the shared codebase since the settings are local to your machine.

To add the relevant entries to the Keychain, run the following commands:

security add-generic-password -a "myapp" -s "release-keyStorePassword" -w "MY_PASSWORD"
security add-generic-password -a "myapp" -s "release-keyAliasPassword" -w "MY_PASSWORD"

The Keychain only applies to Mac OS X, but similar tools exist for both Linux and Windows. It should not be hard to integrate them into the Gradle build in a manner similar to the code above, as long as they provide a command line interface to query passwords.

All the pieces are now in place to sign and release your Android app without risks of committing passwords to the shared codebase.


More posts here.

Comments