DEV Community

Cover image for Avoid uploading API keys and other sensitive info to GitHub on Android, here's how.
Leehendry Pauletto
Leehendry Pauletto

Posted on • Updated on

Avoid uploading API keys and other sensitive info to GitHub on Android, here's how.

Cover image by Bruno Martins @ Unsplash

Disclaimer

  • There’s no measure alone that will make your app secure. Securing an app requires a combination of measures, and even sophisticated ones can eventually be cracked;
  • That said, this tutorial is not a silver bullet for all our security problems, but one of those measures that can make our apps less prone to information leak.

TL;DR

  • API keys are used to authenticate users on specific services;
  • Disclosure of sensitive info such as keys can lead to unintended use and consequences;
  • Gradle's BuildConfig can be used to read sensitive values from a local file and provide them to our project via generated code.

Context

Disclosing sensitive info may result in damage not only to ourselves, but also to users of our projects.

Although I have focused on API keys, other sensitive info like endpoints and credentials can also be kept locally with this approach.

The Whats

First of all, let's make sure we're talking about the same stuff:

  • Client refers to software or hardware making use of services provided by a server, in a client-server model <1>;

  • API stands for Application Programming Interface, which is basically a set of rules you must observe for communication between programs, often a client and a service <2>;

  • API secret, token or key is a unique identifier used to authenticate a client on an API service <3>;

  • Code generation is a process by which a compiler can convert representations of code into ones that can be readily executed by the machine <4>.

The Whys

  • API keys are a way of identifying users and are generated for specific purposes. Due to that, user accounts are often charged second to service use quotas. - Google Maps Platform is a good example of that, as prices vary depending on how services are used.
  • Avoiding public disclosure of such keys is fundamental to prevent unintended service use.

Despite that, API key versioning is a mistake that not only have I committed in the past, but also seen committed by fellow junior and more senior developers alike.

Unfortunately, Information Security -- or Info Sec -- practices are still poorly spread within our developer communities.

The How

  • Gradle's BuildConfig class allows us to read values from local files and then generate code to be used by our app.
  • As long as we correctly add such files to .gitignore, public disclosure of our API keys on GitHub, GitLab or the likes should be less of a problem.

The Code

There are eight steps:

1. Create keys.properties at the root

  • At the root of the project, create a file named keys.properties and add your API key to the file as such:
api.key=API_KEY_HERE
Enter fullscreen mode Exit fullscreen mode

2. Add file path to .gitignore

  • To prevent the versioning of keys.properties, add the file path to .gitignore file as such:
/keys.properties
Enter fullscreen mode Exit fullscreen mode

3. Create keys.gradle at the root

  • In order to retrieve the keys in keys.properties, add to the root of the project a file named keys.gradle, where the following Groovy code should be added:
// Define how to load a string from a properties file:
def getStringFromFile(fileName, property) {
    Properties props = new Properties()

    props.load(project.rootProject.file(fileName).newDataInputStream())

    return props[property].toString()
}


// Specify keys.properties file as the source
def getStringFromKeysFile(property) {
    return getStringFromFile('keys.properties', property).toString()
}


// Expose the key defined in keys.properties as a variable available for the whole project
ext.keys = ["apiKey": getStringFromKeysFile('api.key'),]
Enter fullscreen mode Exit fullscreen mode

4. Import keys.gradle inside project/build.gradle

  • In order to use the methods defined in keys.gradle, import the file inside the build.gradle file of the project:
buildscript {
    apply from: project.rootProject.file("keys.gradle")

    ...

}
Enter fullscreen mode Exit fullscreen mode
  • At this point, BuildConfig is already able to automatically convert the key into compiled code -- but we'll do that on the step 6.

5. Define a constant for the key inside the app/build.gradle

  • Define a constant named API_KEY in the build.gradle file of the app, as follows:
android {

...

    buildTypes {

     // Although it's set to 'debug', certify what build variants (say debug, staging, release) will need to access the key:
        debug {

            ...

            it.buildConfigField "String", "API_KEY", keys.apiKey
        }
}
Enter fullscreen mode Exit fullscreen mode

6. Clean and recompile the project

  • For the code to be effectively generated, select Build > Clean Project and then Build > Rebuild Project at the upper menu of Android Studio.

7. Call the key variable via BuildConfig inside the project

  • Call the constant named in step 5 as BuildConfig.API_KEY where it's needed. For example:
class CustomApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        Firebase.init(this, BuildConfig.API_KEY)
    }
}
Enter fullscreen mode Exit fullscreen mode

8. Share keys.properties in a secure manner inside your team

  • Since we have avoided the versioning of the API key, we need a secure way of sharing the keys.properties file between the members of the development team;
  • If your company has an Info Sec team, check with them what's the best way to share the file;
  • If not, solutions like 1Password may be a good choice to do so, since it offers manners of managing user privileges.

Final considerations

  • Other sensitive info like endpoints and credentials can also be kept locally with this approach;
  • Names like keys.properties, api.key, keys.gradle and API_KEY are all arbitrary -- just keep their extensions (.properties, .key, .gradle) when renaming them.
  • NOTE THAT BuildConfig generates compiled code, which means that reverse engineering our app's .apk could still expose API keys and other secrets;
  • Only a combination of security measures can help us keep our apps secure. Look into code optimisation and obfuscation tools such as R8 to diminish the chances of info leaks.

Special thanks to Victor Vieira for teaching me the approach a year back; and to Walmyr Carvalho for stimulating me to write on dev.to!

Discussion (0)