Get your copy of the book

Transforming the Purchasing Experience

Download
Ebook Retail Transformation Technology

Keeping Secrets Safe in Android Projects

  —  
 read

My Cryptography course professor used to say: “Nothing is 100% secure. Even if you go to the farthest place to hide the key for a safe, there is still a small percentage of probability that somebody will find it”.

Making something more secure actually means making sure that percentage is as low as possible.

What can we do to make secrets more secure in Android projects?

The secret in Android projects can be an API key, Keystore, credentials for publishing, credentials for some special access to the 3rd party SDK, etc. If we want to secure a secret in an Android project, we usually ignore it in Git. When somebody new jumps into the project, those ignored files should be added manually.

Usually, secrets, like credentials, are inside the build.gradle file because they are environment-specific. For simplicity, let’s say that we have an environment called preproduction in which we have credentials to log in automatically into the application tapping on the login button multiple times.

This environment acts the same as production plus this log in automatically feature. This is good to have for fast internal testing on production environment before production release. However, this secret should never be visible in the final production apk. For simplicity, let’s say that we have CREDENTIALS that we use only in preproduction environment.

We can add it to build.gradle file :

...
defaultConfig {
    ...
    resValue RES_STRING, CREDENTIALS_USERNAME,''
    resValue RES_STRING, CREDENTIALS_PASSWORD,''
    ...
}

productFlavors {
    preproduction {
            ...
            resValue RES_STRING, CREDENTIALS_USERNAME,'mladen'
            resValue RES_STRING, CREDENTIALS_PASSWORD,'str0ngP4ssw0rd'
            ...
    }
}
...

When we build the project, the credentials strings are located in gradleResValues.xml and we can access them using @string/ in XML files or getString function in Kotlin/Java classes as we do for any other string from resources.

build.gradle file should not be ignored in the version-control system cause all developers in the team should have the same version. With the current implementation, ‘CREDENTIALS_USERNAME’ and ‘CREDENTIALS_PASSWORD’ as part of the build.gradle file, will be included in git. This is not safe.

We want to make it more secure, so if somebody gets access to the repository that person should not get access to our production credentials.

Credentials

To avoid that, we can store this key in local.properties file:

CREDENTIALS_USERNAME=mladen
CREDENTIALS_PASSWORD=str0ngP4ssw0rd

local.properties file should not be included in the version-control system.

To make keys from local.properties accessible in build.gradle we can add the following function:

def getLocalProperties() {
    Properties props = new Properties()
    if (file('../local.properties').exists()) {
        props.load(new FileInputStream(file('../local.properties')))
    }
    return props
}

Next, we should create a build config field this way:

preproduction {
    ...
    Properties localProperties = getLocalProperties()
    resValue RES_STRING, CREDENTIALS_USERNAME, localProperties["CREDENTIALS_USERNAME"]
    resValue RES_STRING, CREDENTIALS_PASSWORD, localProperties["CREDENTIALS_PASSWORD"]
    ...
}

How will CI know about this secret?

At Infinum, we switched from CircleCI to Bitrise, so in this blog post, I'll show you how to do this on Bitrise, but it's similar to any other CI.

We should copy the key from our local.properties file and add it inside Bitrise Secret ( Project -> Workflow -> Secrets -> Add New). Let’s name it CREDENTIALS_USERNAME.

Next, we should change build config field:

resValue RES_STRING, ‘CREDENTIALS_USERNAME’, System.getenv("CREDENTIALS_USERNAME") ?: project.properties["CREDENTIALS_USERNAME"]

If we run the build on CI, it will get CREDENTIALS_USERNAME from Secrets, if we run it locally it will get API_KEY from local.properties file.

What if we want to have a special credentials.properties file with all API keys?

If we want to separate API keys from other properties in local.properties file, we can create a new credentials.properties file. Moreover, instead of storing key by key in Bitrise Secrets, we can store the whole content of credentials.properties file in one Bitrise secret called CREDENTIALS_PROPERTIES.

Then, we can make a special workflow step that will create credentials.properties file and fill it with data stored in Bitrise Secret. That step should be called before any other step.

Bitrise Secret

A script could be made as follows:

set -e
echo "${CREDENTIALS_PROPERTIES}" > credentials.properties

In that case we should have only the following, without fetching the key from the system envs.

resValue RES_STRING, CREDENTIALS_USERNAME, project.properties["CREDENTIALS_USERNAME"]

You can choose one of the two approaches depending on what works for you.

Sharing secrets between team members with Vault

Adding secrets to the CI is a one time thing. You add them and you won’t change them ever, but what if you need to share them with your colleagues? It’s possible to use a password manager, but when someone joins the project, they have to add a new file and copy paste the credentials. If you have a lot of them, this can be tedious.

A better way to share secrets is by using Vault. This tool enables you to have secrets for all your projects in one place. It has a command line tool, so you can integrate it with your continuous integration script. That way, you do not expose secrets on your CI as they will be fetched only when you need them.

Any colleague who joins the project can have access to the Vault and will be able to fetch all or only one secret.

There are other tools which you can use instead of Vault, but the idea is the same. You have one tool which will help you share those secrets in your team.

Is this secure enough?

If someone wants to, they can still do reverse engineering of the apk file and find keys, so it is really important what you have in a final production apk. If somebody gets access to the repository, credentials that are not included in the final production apk but are used during the development should not be exposed to that person.

Also, it is really important that those apks for preproduction environment will be stored in some internal host.

This was just one example of how we make Android projects more secure. In the end, everything is vulnerable, but we should try to make it harder for the bad guys. By introducing more layers of security, we reduce the vulnerability.

Vault-watcher Ivan Kocijan assisted with writing the section on Vault, while key-keeper Petra Grubišić designed the cover illustration.