How to protect your API keys in Android 🔐

Android environment variables, buildConfigFields and resValues.

Elad Gelman
4 min readMar 12, 2021
Photo by Danial Igdery on Unsplash

In this article I’m going to discuss the issues you may encounter when storing api keys in your codebase and different methods to solve these issues.

You start a new Android project: cue rainbows and butterflies!
Your app/build.gradle will look like this:

android {
compileSdkVersion rootProject.ext.compileSdkVersion
signingConfigs {
staging {
...
}

production {
...
}
}

defaultConfig {
renderscriptTargetApi 23
renderscriptSupportModeEnabled true
applicationId "com.blasting.rocketship"

}


buildTypes {
debug {
debuggable true
applicationIdSuffix ".debug"
}

staging {
debuggable false
applicationIdSuffix ".staging"
signingConfig signingConfigs.staging
}

release {
debuggable false
signingConfig signingConfigs.production
}
}
}

At some point along the way you start using an SDK. That SDK has a key, and if you follow the instructions on the developer pages of the SDK’s provider, you usually end up writing a function in your Application.java file that looks like this:

For this post we are using the LaunchFlaconHeavy SDK
@Override
public void onCreate() {
super.onCreate();
LaunchFlaconHeavySDK.initialize(this, "a_no so_long_piece_of_text_compound_of_letters_and_numbers", "another_very_long_piece_of_text_compound_of_letters_and_numbers");
}

Now you’ve written your code and, after a couple back-and-forths, the integration works. You are happy.

Are you though? You realize that on the staging version you want to use one API key and on the production version a different one. You don’t want to get the data from the the two environments mixed.

To solve this issue, you need to create a strings.xml file for every env(debug/staging/release). It will look like this:

Debug
<resources>
<string name="falcon_heavy_sdk_app_id">"Debug not so long app id"</string>
<string name="falcon_heavy_sdk_api_key">"Debug Long API key"</string>
</resources>
Staging
<resources>
<string name="falcon_heavy_sdk_app_id">"Staging not so long app id"</string>
<string name="falcon_heavy_sdk_api_key">"Staging Long API key"</string>
</resources>
Release
<resources>
<string name="falcon_heavy_sdk_app_id">"Release not so long app id"</string>
<string name="falcon_heavy_sdk_api_key">"Release Long API key"</string>
</resources>

Once you create your string.xml, your Application.java now looks like this:

For this post we are using the LaunchFlaconHeavySDK
@Override
public void onCreate() {
super.onCreate();
LaunchFlaconHeavySDK.initialize(
this,
R.string.falcon_heavy_sdk_app_id,
R.string.falcon_heavy_sdk_api_key);
}

You are amazing!

A couple of days go by, you are on the beach, drinking your cerveza, proud of your accomplishment. Then the phone rings and it’s your manager. “There has been a breach to our code base and all of our code was stolen, please tell me you didn’t push the FalconHeavySDK API Key.”

Your cerveza hits the floor as you stutter out an, “Um.”

You’re fired and become a digital nomad. You spend your life trying to build a time machine to take you back to that day so you can take the g*d d**n API keys out of your code (plus, buy all the bitcoin you can get your hands on!).

Or you can just do the following:

First remove the API keys from your string.xml:

Debug
<resources>
It's so empty here
</resources>
Staging
<resources>

</resources>
Release
<resources>

</resources>

Your app/build.gradle will now look like this:

android {
compileSdkVersion rootProject.ext.compileSdkVersion
signingConfigs {
...
}

defaultConfig {
renderscriptTargetApi 23
renderscriptSupportModeEnabled true
applicationId "com.blasting.rocketship"
resValue 'string', 'falcon_heavy_sdk_app_id', getEnvVarValueAndThrowException('FALCON_HEAVY_SDK_APP_ID')
resValue 'string', 'falcon_heavy_sdk_api_key', getEnvVarValueAndThrowException('FALCON_HEAVY_SDK_API_KEY')

}


buildTypes {
...
}
}

When we declare:

resValue 'string', 'falcon_heavy_sdk_app_id', getEnvVarValueAndThrowException('FALCON_HEAVY_SDK_APP_ID')

we tell gradle to generate a string resource named falcon_heavy_sdk_app_id.

And before you ask me what the getEnvVarValueAndThrowException method is, let me tell you.

It’s a method we will declare before the android { :

def getEnvVarValueAndThrowException(key){
def retVal = System.getenv(key)
if(null == retVal) {
throw new GradleException("Required ${key} environment variable not set.")
}

return retVal
}
android {

What this method does, is that it gets a value from an environment variable. If the value is missing an exception would be thrown, thus stopping the compilation and warning us that we have forgotten to input the value. We don’t want to launch a missile without the launch codes now do we?

Next we will declare our keys/ids in our bash_profile:

export FALCON_HEAVY_SDK_APP_ID="our not so long app id here"
export FALCON_HEAVY_SDK_API_KEY="our really long API key here"

It works for any CI as well. We devs always say, “An env variable is an env variable. No matter where you are.”

Is that all? No…

Let’s get greedy.

Remember this piece of code?

release {
debuggable false
signingConfig signingConfigs.production
buildConfigField 'boolean', 'IS_LUNCH_FALCON_HEAVY', 'true'
}

buildConfigField is another way for us to declare a variable that will be available after building the project and syncing the gradle files.

If we take the example above, the Application.java will look like this:

@Override
public void onCreate() {
super.onCreate();
LaunchFlaconHeavySDK.initialize(
this,
BuildConfig.FALCON_HEAVY_SDK_APP_ID,
BuildConfig.FALCON_HEAVY_SDK_API_KEY);
}

Now we change the app/build.gradle file.

release {
debuggable false
signingConfig signingConfigs.production
buildConfigField 'string', 'IS_LUNCH_FALCON_HEAVY', 'true'
buildConfigField
'String', 'FALCON_HEAVY_SDK_APP_ID', "\"${getEnvVarValueAndThrowException('FALCON_HEAVY_SDK_APP_ID')}\""
buildConfigField 'String', 'FALCON_HEAVY_SDK_API_KEY', "\"${getEnvVarValueAndThrowException('FALCON_HEAVY_SDK_API_KEY')}\"" }

Is that all? Yes! With this method you’re now able to store your API keys separately from your codebase and use them at complication time whether it’s your development computer or your CI machine

Happy coding!

Photo by Ricky Kharawala on Unsplash

--

--