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
2. Add file path to .gitignore
- To prevent the versioning of
keys.properties
, add the file path to.gitignore
file as such:
/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 namedkeys.gradle
, where the followingGroovy
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'),]
4. Import keys.gradle
inside project/build.gradle
- In order to use the methods defined in
keys.gradle
, import the file inside thebuild.gradle
file of the project:
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 thebuild.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
}
}
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)
}
}
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
andAPI_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!
Top comments (0)