Safely sign your Android app with CircleCI

Julien Repele
Cheerz Engineering
Published in
4 min readMar 27, 2019

I had a dream; generate a beta release apk as close as possible to production ones.

For testing purposes, we really wanted to generate some Android apps with a config super close to the production one. Same API, same naming and content, same Google API enabled…

Also, we wanted to industrialize the process and do not spend any development time on each build. So Product Owners and tester can always pickup the very last version of the app.

We strongly believe that building a release app should not be an event. If we do it frequently enough, that process become simple.

Thus, we needed to build our app on in a continuous integration process (CircleCI in our case) and sign it before providing the apk to our testers.

A lot of people managed to do this by saving their non-encrypted release.keystore file in a Dropbox repository. Even if it works, we already use Github as a provider for our application files. Why using another service? I don’t want to develop and maintain a script only made for this.
Also, I don’t want to share the file without any encryption.

So why not just encrypt the file and share it via Github? Well this article is about this very subject, let’s dive into it.

Encrypt and share your keystore

Generate a random password

First things first, generate a proper password. There is plenty way of doing it. I used openssl because it was already installed on my computer. But actually it doesn’t really matter.

openssl rand -base64 32

This password will be use for encrypt and decrypt the keystore. Remember it because you will need to add it in CircleCI environment variables later.

Encrypt the keystore

To encrypt the key store, we will use the GPG tool (GNU Privacy Guard). It’s super easy to use and above all, it’s available in every GNU/Linux distribution. If you’re on macOS and gpg it’s not already installed, you can use homebrew to get it:

brew install gnupg

Ok so now encrypt the keystore file with the password generated at the previous step:

gpg --symmetric --cipher-algo AES256 --passphrase <password> release-key.keystore

Please use the AES256 algorithm to benefit from symmetric encryption; the same key will be used to encrypt and decryption.

Fetch your keystore

Once your keystore file is encrypted, create a Github repo and push the file. You can now safely add this repository as a submodule in your project. CircleCI will need an access to fetch this repo.

git submodule add <your_git_repo>
git submodule init
git submodule update

You can add this method to you config.yml file to fetch the submodule on your CircleCI instance:

fetch_submodules: &fetch_submodules
run:
name:
Update submodules
command: git submodule sync && git submodule update --init

Decrypt your key store

Now we need to share the password used for the release.keystore file encryption to CircleCI.

Add your KEYSTORE_ENCRYPTION_KEY to CircleCI environment variables:

Create a bash file with a method to decrypt the keystore file (for example .circleci/cirlce_env.sh). Don’t worry about the KEYSTORE_FILE variable, we will add it in the CircleCI environment variable in the next paragraph.

function fetchKeystore {
sudo gpg --passphrase ${KEYSTORE_ENCRYPTION_KEY} --pinentry-mode loopback -o "app/$KEYSTORE_FILE" -d "<path_to_keystore>/$KEYSTORE_FILE.gpg"
}

Once again, we use the GPG tool to decrypt the file. Please fill the path_to_keystore with you own path.

For ease of use, add a method to your CircleCI config file (config.yml) and call it in your Android build job.

fetch_keystore: &fetch_keystore
run:
name: Fetch keystore
command: source .circleci/circle_env.sh && fetchKeystore

Configure Gradle

So now we need to give our keys to CircleCI in order to sign the app. Add the following variables to the CircleCI environment variables:

  • KEYSTORE_FILE ( basicaly, the name of the file;release-key.keystore here)
  • KEYSTORE_PASSWORD (the one used for your signed releases)
  • KEY_ALIAS (the one used for your signed releases)
  • KEY_PASSWORD (the one used for your signed releases)

Finally, let’s configure our Gradle build to use the keystore to sign the app.

function copyEnvVarsToGradleProperties {
DIR="$(pwd)"
GRADLE_PROPERTIES=${DIR}"/<path_to_gradle.properties>"
export GRADLE_PROPERTIES
echo "Gradle Properties should exist at $GRADLE_PROPERTIES"

if [[ ! -f "$GRADLE_PROPERTIES" ]]; then
echo "Gradle Properties does not exist"

echo "Creating Gradle Properties file..."
touch $GRADLE_PROPERTIES

echo "Writing keys to gradle.properties..."
echo "KEYSTORE_FILE=$KEYSTORE_FILE" >> ${GRADLE_PROPERTIES}
echo
"KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" >> ${GRADLE_PROPERTIES}
echo
"KEY_ALIAS=$KEY_ALIAS" >> ${GRADLE_PROPERTIES}
echo
"KEY_PASSWORD=$KEY_PASSWORD" >> ${GRADLE_PROPERTIES}
fi
}

Launch the build

create_release_credits: &create_release_credits
run:
name:
Create fake credits
command: source .circleci/circle_env.sh && copyEnvVarsToGradleProperties

Wrapping up everything

This is the .circleci/cirlce_env.sh file with the methods to decrypt the keystore file and to setup the gradle properties.

Here is an example of the CircleCI config.yml file with the job to sign and build your app.

Workaround

One other solution for the release keystore sharing would have been to save as the environment variable the encrypted content of the file.

I personally prefer to have the file on a Github repo. That way, I can use the same repo to provide the keystore on another computer or on another continuous integration service.
But this is up to you ;)

Do you want to see more than what we’ve shown here?

Don’t hesitate to contact us, it would be a pleasure to talk, or even to welcome for you for a coffee at our office (France 🇫🇷-> Paris 🏙-> Place de Clichy / Saint Lazare 🚊)!

Just so you know, we are hiring engineers! Don’t hesitate to contact us, or check our page here!

--

--