I recently open-sourced a tool called secrets-gradle-plugin — a Gradle plugin that allows you to pass secrets, such as API keys, securely into an Android app. While the plugin was originally developed for apps that use Google Maps Platform Android SDKs, the problem it solves is generic enough and can be applied to other third party libraries. It works by reading key-values from a secure properties file and exposes those values in two ways. First, as constants in your BuildConfig.java file, so your keys can be programmatically accessed, and second, as injected build variables in your AndroidManifest.xml file, so keys can be passed to libraries that require a manifest entry. The properties file should be secure in that it should not be checked into version control (not strictly enforced by the plugin), or the file itself should be encrypted in some way (tools like git-secret can help with this).
The plugin was designed with three concerns in mind — security, scalability, and ease of use — which I’ll go over here. If you want to skip ahead and try out the plugin, you can check it out on Github.
Clients typically authenticate into services and SDKs by passing an API key. This allows the provider to infer who you are and permit you to access resources useful to you. For example, for a financial services application, an API key might allow you to access your transactions, whereas for an SMS service, an API key could allow you to send SMS messages in bulk. If these services were exposed through a website, you would have to authenticate first with a username and password combination. So in a sense, API keys are proxies to your identity and should be securely stored at all costs.
API keys are proxies to your identity and should be securely stored at all costs.
Securing an API key can come in different forms, and like any engineering challenge, have different tradeoffs to consider.
For example, you can encrypt your API keys and use obfuscation techniques so that you can safely store the encrypted version of a key in version control. Yet another approach is to store API keys off of the client altogether and provide them from a server. As you might’ve guessed, both of these approaches run into the same problem since a key has to be used both for encryption and for authenticating into a server providing your API keys. There is no silver bullet to solve these sorts of challenges, and security typically comes in the form of combining different methods that fit your requirements.
Now in the context of one of the libraries I support — Maps SDK for Android — the techniques I mentioned above aren’t options since authentication needs to be done through a manifest entry. For Maps specifically, the best thing you can do from the lens of security is to apply restrictions to it and to make sure your keys are as private as possible. That is, restrict visibility by not exposing keys in version control (even in private repositories) or to other applications.
The secrets-gradle-plugin solves the visibility restriction problem by reading keys from a properties file ignored by version control. By default, it reads secrets from local.properties which should be in .gitignore for most projects.
If your project already hides keys off of version control and reads it from a private file — that’s great! Adding new keys, however, usually involves the following steps:
- Update properties file with your new key
- Update build.gradle file to expose the new key in your BuildConfig, manifest variable, or string resource (don’t this!)
- Use the key
The plugin simplifies this process a bit by eliminating the second step since it will generate a corresponding BuildConfig value and manifest variable for all keys within your secrets file (you can also configure it to ignore keys as well). This approach scales well as you add more keys over time.
Note that the plugin intentionally does not generate string resource values for secrets since string resources are world-readable.
Ease of Use 😌
The plugin aims to be as simple as possible by requiring minimal step up, using defaults that apply to most, and adding configuration options when defaults don’t apply.
To use this plugin, add the following line to your app-level build.gradle file and start adding API keys to your root project’s local.properties file.
id 'com.google.secrets_gradle_plugin' version '0.4'
Once you rebuild your project, you’ll be able to access your keys programmatically and in your manifest file.
…and that’s about it! If these defaults don’t apply to you, you can always configure it to fit your needs using the secrets block.
// Provide a properties file with default values for secrets
defaultsPropertiesFileName = “local.defaults.properties” // Ignore all keys matching regex "sdk.*"
While this plugin was developed to fit very specific needs for Google Maps Platform Android SDKs, it makes no such assumption on the libraries you are using. But, if the plugin doesn’t quite fit your use case, feel free to create an issue on GitHub, or better yet, send a pull request.
EDIT (2/13/2021): It’s been pointed out by several readers that so long as your API key is in code, it can easily be recovered by decompiling the APK. Yes, this is in fact the case. Additional security measures (like adding key restrictions) should be done as well. But to draw an analogy, just because you can pick a lock, doesn’t mean you should leave your keys out in the open!