The Social App: Setting up the app signature

Rafael Toledo
The Social App
Published in
4 min readApr 25, 2018

--

If you don’t know about this project’s motivation, read the first post here.

In the previous post, we configured our build script to support Kotlin. Today, we’ll set up the app’s signature and raise an important point: how to store our secrets, especially when the app is open source? Just to check how significant is this, try to search a little on Github, and check how many repositories you will find, with all kinds of private information, from API keys to paid tools. All of them, accidentally committed (or even deliberately, but inadvertently) in the public repository of the project.

On Android, one of the biggest “secrets” we need to keep is the signature key of the app. It, along with the Application ID, makes an app unique. As the identifier is public, a key leak can create a severe security risk. It can result on (maliciously) modified versions of your app that can be installed over the original app, not only enabling potentially dangerous behavior for the user, such as access to information internal and private, that your app may have saved locally (such as authentication data in your API, for example).

The loss of this key can also be a big problem too (and that’s the reason most of the people adds it to the repository). Without it, it’s impossible to send updates from your app to Google Play, for example. Today, fortunately, we already have ways to minimize that risk by using Google Play App Signing (a subject that we will undoubtedly address in this series of posts).

Speaking specifically about our app, we then have a problem with that. For the signature, we need a file (usually with the extension .keystore or .jks), two passwords and an alias. The first question is about the file itself: will we add it to the repository? As the key is password protected only, would be good to not add it directly to the repository.

To solve this problem, we have two approaches: first, we can place the signature file in some external service, such as Google Drive or Dropbox, and download it at the moment of the build using curl. In this scenario, we would have an API key exposed via the environment variable. Although feasible, this scenario has the problem of adding an external dependency on the simple APK build process. In addition to network oscillations that may cause the build to fail, we must also need to keep this request up to date, having to adjust the download call in case of any changes to the APIs of those services.

Another approach (the one I’m going to use here) is to encrypt the file and put it in the repository along with the project. In the CI server, we will export an environment variable which contains the key that can decrypt the file, as well as the other keys needed to sign the APK.

The simplest way to generate a key is through Android Studio itself.

With the key generated, we will use OpenSSL to encrypt the file. The encryption can be achieved by running the command:

openssl aes-256-cbc -e -in release.keystore -out release.keystore-cipher -md sha256 -k $CIPHER_DECRYPT_KEY

Note that in the command we have a variable called CIPHER_DECRYPT_KEY, which is the key that will decrypt our file on the CI server.

With the encrypted file generated, we’ll create a folder in the project to store the keys. In addition to the encrypted file release.keystore-cipher, I’m also placing the file that signs the debug releases. Doing this is particularly useful when more than one person is working on the project, or when there is eventually a need to swap or format the machine, as this key is generated whenever a new installation of the Android SDK is performed.

With the files in the project, let’s first tell the CI to decrypt the file before executing the build itself. Notice that we added a step called Decrypt release key to our config.yml file. I also modified the build step to invoke the task assemble instead of build (to validate the signature of both build variants):

version: 2
jobs:
build:
docker:
- image: circleci/android:api-27-alpha
working_directory: ~/social-app environment:
JVM_OPTS: -Xmx3200m
CIRCLE_JDK_VERSION: oraclejdk8
steps:
- checkout
- restore_cache:
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
- run:
name: Accept licenses
command: yes | sdkmanager --licenses || true
- run:
name: Decrypt release key
command: openssl aes-256-cbc -d -in distribution/release.keystore-cipher -out distribution/release.keystore -md sha256 -k $CIPHER_DECRYPT_KEY
- run:
name: Build
command: ./gradlew clean check assemble assembleAndroidTest
- save_cache:
paths:
- ~/.gradle
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}

After that, edit our build script to configure the signature and assign them to each of the build variants:

...
android {
...
signingConfigs {
debug {
storeFile file("$rootDir/distribution/debug.keystore")
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
release {
storeFile file("$rootDir/distribution/release.keystore")
storePassword System.env.RELEASE_STORE_PASSWORD
keyAlias System.env.RELEASE_KEY_ALIAS
keyPassword System.env.RELEASE_KEY_PASSWORD
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
...

Note that the the release signature will pick up the data (passwords and alias) from the environment variables, which will be added to CircleCI:

And that’s it! The Pull Request was opened and approved, having passed in the CI and integrated into the branch develop of the repository.

In the next post, we’ll setup Firebase in the project!

Thanks for reading!

--

--