Where should you store your secret keys in Android?
Almost every Android app has some data that should not be exposed. Secret credentials, end point URLs and API keys are examples of data that one cannot afford to compromise. So where should you store this information?
Let me just start out by saying that if you want 100% security, then don’t place such data in your Android app, as neither of the methods discussed below are bullet proof. You can give a hacker a hard time using the these methods, but they can still be cracked. For guaranteed security, place your secret data on the backend. Save your secret data in a separate server that no other parties have access to. It might not always be feasible to do this, and you might just need to store the secret data in your app itself. Let’s checkout the best practices to do this.
Hide the data in BuildConfigs
Open the existing gradle.properties
file. We will add all the secret keys in this file and then read them inside the module level gradle file app/build.gradle
. You may create a separate properties file if you don’t wish to alter the gradle.properties
file.
API_KEY = "ABC"
BASE_URL = "https://xyz.in"
Add this properties file to .gitignore so that it does not show up in your Github repository. If you used gradle.properties then move on to the next point. If you created a separate properties file, then you need to load that file in the app/build.gradle
file as follows:
def secretPropertiesFile = rootProject.file("secrets.properties")
def secretProperties = new Properties()
secretProperties.load(new FileInputStream(secretProperties))
Now add the secrets to the buildConfigField
s
android {
...
defaultConfig {
...
buildConfigField("String", "API_KEY", API_KEY)
buildConfigField("String", "BASE_URL", BASE_URL)
...
}
}
If you made a separate properties file, then you app/build.gradle
should look like this:
android {
def secretPropertiesFile = ootProject.file("secrets.properties")
def secretProperties = new Properties()
secretProperties.load(new FileInputStream(secretProperties)) defaultConfig {
...
buildConfigField(
"String",
"API_KEY",
secretProperties['API_KEY']
)
buildConfigField(
"String",
"BASE_URL",
secretProperties['BASE_URL']
)
}
You’re done!
Gradle provides a BuildConfig
object for you to access these config fields anywhere in your code:
// Kotlin
val apiKey = BuildConfig.API_KEY
val baseUrl = BuildConfig.BASE_URL// Java
String apiKey = BuildConfig.API_KEY;
String baseUrl = BuildConfig.BASE_URL;
Hide the data in a resource file
Dedicate a resource file for holding all your secrets in one place. Let’s call this file “secrets.xml”. Add all your secrets as String resources as shown below:
<resources>
<string name="api_key">ABC</string>
<string name="base_url">https://xyz.in</string>
</resources>
You can now access them just like you would access any other string resource.
// Kotlin
val apiKey = applicationContext.resources.getString(R.string.api_key)
val baseUrl = applicationContext.resources.getString(R.string.base_url)// Java
String apiKey = getApplicationContext().getResources().getString(R.string.api_key);
String baseUrl = getApplicationContext().getResources().getString(R.string.base_url);
Add “secrets.xml” to your .gitignore
file to make sure it does not popup in your Github repository.
These two methods work best when your primary goal is to hide certain files using .gitignore
file after publishing your project online (GitHub, Bitbucket, etc). But they are not very secure. In fact, the second approach is just bananas when it comes to security. Resource files can be decompiled from your app package without much effort. Let’s checkout some other ways that make the reverse engineering process more difficult.
Code Obfuscation
Obfuscation guards your application against reverse engineering. In simple words, it alters the structure of your code, renames classes, functions, fields, libraries, etc in such a way that the code functions then same way, but it becomes extremely difficult to disassemble and decompile the code and even more difficult for human eye to parse the retrieved code. Let’s say you have a field in your class called “mBaseUrl”. After code obfuscation, this field’s name could change to “a”. Now imagine how difficult it will be to read code where all fields, method names and classes have meaningless names like this. You can use ProGuard for this purpose. ProGuard is a tool that optimizes, obfuscates and minifies (removes unnecessary code and resources) your code.
Android Keystore
Using Android Keystore system you can store cryptographic keys in a container making it more difficult to extract from the device. Keystore uses public key and symmetric key cryptography to safeguard secrets. Going in depths of keystore is beyond the scope of this article; I will talk about it in my future posts and update the link here. Do checkout the Android documentation for Keystore and Jetpack security library
Storing secrets in native libraries using NDK
You can also store your secrets in the native C/C++ class and then access them in your Kotlin/Java class. Here is a great medium article about it.