Avoid uploading API keys and other sensitive info to GitHub on Android, here's how.
Tags: android, tutorial, security, github
Published: 2020-12-03 22:39:33

######Cover image by [Bruno Martins @ Unsplash](https://unsplash.com/@brunus)

#**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](https://www.techtimes.com/articles/246148/20191122/can-quantum-computing-break-the-blockchain.htm);
- 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](https://www.zdnet.com/article/personal-data-of-16-million-brazilian-covid-19-patients-exposed-online/).

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>](https://techterms.com/definition/client-server_model);

- **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>](https://techterms.com/definition/api);  

- **API secret, token or key** is a unique identifier used to authenticate a client on an API service [<3>](https://en.wikipedia.org/wiki/Application_programming_interface_key);

- **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>](https://en.wikipedia.org/wiki/Code_generation_(compiler)).

###**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](https://cloud.google.com/maps-platform/pricing).
- 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](https://developer.android.com/studio/build/gradle-tips) 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:

```groovy
api.key=API_KEY_HERE
```

####2. Add file path to `.gitignore`
- To prevent the versioning of `keys.properties`, add the file path to `.gitignore` file as such:

```groovy
/keys.properties
```

####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:

```groovy
// 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'),]
```

####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:

```groovy
buildscript {
    apply from: project.rootProject.file("keys.gradle")

    ...

}
```
- 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:

```groovy
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
        }
}
```

####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:

```kotlin
class CustomApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        Firebase.init(this, BuildConfig.API_KEY)
    }
}
```

####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](https://developer.android.com/studio/build/shrink-code) 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!_