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 in the 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 the preproduction environment.
We can add it to the 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.
Thebuild.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.
To avoid that, we can store this key in the local.properties file:
CREDENTIALS_USERNAME=mladen
CREDENTIALS_PASSWORD=str0ngP4ssw0rd
The 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 the 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 thecredentials.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.
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 that you can use instead of Vault, but the idea is the same. You have one tool which will help you share those secrets with 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.