Android: Hide API keys in native libraries using NDK
If you are reading this blog then you must be an awesome Android developer or you are going to be one in the future. As an Android developer, we make Android applications and use several third-parties libraries (SDK tools) because it is impossible to develop everything by ourselves.
Often your app will have secret credentials or API keys that you need to have in your app to function but you’d rather not have easily extracted from your app.
For storing fixed API keys, the following common strategies exist for storing secrets in your source code:
- Hidden in BuildConfigs
- Embedded in resource file
- Obfuscating with Proguard
- Disguised or Encrypted Strings
- Hidden in native libraries with NDK
- Hidden as constants in source code
This process is not bulletproof. As resources, they are somewhat more vulnerable to the decompilation of your application package, and so they are discoverable if somebody really wants to know them. However, this solution does prevent your secrets just sitting in plaintext in source control waiting for someone to use and has the advantage of being simple to use, leveraging Android’s resource management system, and requiring no extra libraries.
However, none of these strategies will ensure the protection of your keys, and your secrets aren’t safe. The best way to protect secrets is to never reveal them in the code in the first place. Compartmentalizing sensitive information and operations on your own backend server/service should always be your first choice.
If you do have to consider a hiding scheme, you should do so with the realization that you can only make the reverse engineering process harder and may add significant complications to the development, testing, and maintenance of your app in doing so. Check out this excellent StackOverflow post for a detailed breakdown of the obfuscation options available.
If you reveal your secrets to the wind, you should not blame the wind for revealing them to the trees. ~Khalil Gibran
Prerequisites
Before starting, please install NDK(Side by side) from the Tools > SDK Manager > SDK Tools. Click on Apply and after downloading and installing click on OK.
Now, we are done with downloading the tools, let’s quickly move on to securing the API keys using NDK.
Implementation
To compile the native code present in the project, Android Studio supports the ndk-build. Here, you will be having an Android.mk build file that is used by ndk-build.
The
Android.mk
file resides in a subdirectory of your project'sjni/
directory, and describes your sources and shared libraries to the build system. It is really a tiny GNU makefile fragment that the build system parses once or more. TheAndroid.mk
file is useful for defining project-wide settings thatApplication.mk
, the build system, and your environment variables leave undefined. It can also override project-wide settings for specific modules.
Follow the below steps to use ndk-build:
Step1: Under the app/src/main directory, create a directory named jni. Here we will be having our .mk files and the native code file.
Step2: In the jni directory that you have created in the previous step, add one file named Android.mk and add the below lines of code to it:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := app-keys
LOCAL_SRC_FILES := app-keys.c
include $(BUILD_SHARED_LIBRARY)
The description of the above code:
- The LOCAL_PATH variable indicates the location of the source file. my-dir is a macro function provided by the build system to return the current directory.
- The CLEAR_VARS is used to clear many LOCAL_XXX variables for you like LOCAL_MODULE, LOCAL_SRC_FILES, etc. It doesn’t clear LOCAL_PATH.
- The LOCAL_MODULE variable stores the name of the module that you want to build. The module name must be unique and you shouldn’t use any space in the module name.
- The LOCAL_SRC_FILES variable contains a list of C or C++ files that are present in the module.
- The BUILD_SHARED_LIBRARY variable is used to tie everything together. It determines what to build, and how to do it. It collects the information that you defined in LOCAL_XXX variables since the most recent include.
Step3: In the jni directory, create another file named Application.mk file and add the below code:
APP_ABI := all
The Application.mk file is used to specify the project-wide settings for the ndk-build.
The variable APP_ABI is used to specify the ABIs whose code should be generated by the build system. By default, the build system generates the code for all non-deprecated ABIs.
Step4: The last file to be added in the jni directory is your native code file. So, in the jni directory, add one file named app-keys.c and add the below code into it:
#include <jni.h> //For first API key JNIEXPORT jstring JNICALL Java_com_myapplication_APILib_getAPIKey(JNIEnv *env, jobject instance) {
return (*env)-> NewStringUTF(env, "YOUR_API_KEY");
}
Step5: After adding the required files in your jni directory, our next aim is to provide the path of our Android.mk file in the build.gradle file.
android {
…
defaultConfig {
…
}
buildTypes {
…
}
externalNativeBuild {
ndkBuild {
path ‘src/main/jni/Android.mk’
}
}
}
Step6: Now, to use your API key in the Activity or in any file, go to the Activity or the file where you want to use your API keys. To load the native code that you have written, you need to call the System.loadLibrary(“native-lib”) method in the init block.
init {
System.loadLibrary("native-lib")
}
Step7: Now, declare a Kotlin external function with the same name as used in the native code.
external fun getAPIKey(): String
Step8: Finally, you can get the API key by calling:
APILib.getAPIKey()
That’s all! You have secured your API key :)
If you liked this post, kindly give me some claps and follow me for more posts like this one. Be sure to leave a comment if you have any thoughts or questions.