Securing API Keys using Android NDK (Native Development Kit)

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 not possible to develop everything by ourself. May be you have to use Google maps service or any Google play services. Most of these services are paid and it becomes an important task to secure the API keys of these libraries in our application’s code. Some of you might have used the XML or gradle approach to secure the API keys but these can be easily exposed by reverse engineering.

If you reveal your secrets to the wind, you should not blame the wind for revealing them to the trees.

So, in this blog, you will learn how to secure API keys using the Android Native Development Kit. To make it easier to understand, I have divided the whole blog into the following topics:

  • Current Problem
  • Proposed Solution
  • How to do this in Android? (three ways)
  • Closing Note

Even after using the Android NDK approach to secure the API keys, by doing the reverse engineering thing, you can still get those API keys but the fact here is that we are just making extra layers for securing the API keys because something is better than nothing and we have everything ;) So, let’s get started.

Current Problem

While developing Android applications, we use various third-parties libraries that make the development of our application fast. In these libraries, we just call some functions according to our need and we don’t know the code of these functions and we don’t care about that. But to call these functions, we need API keys that are different for different users. Some of these libraries are paid and if somehow, someone gets your API key then you might land to high payment bills and many other problems. During the starting days to Android development, we put our API keys either in strings.xml file or in gradle file. Following is an example of storing an API key in strings.xml file:

<resources>
    <string name="app_name">SecureAPI</string>
    <string name="MyAPIKey">YOU_AWESOME_API_KEY_HERE</string>
</resources>

The problem with approach is that anyone can get the API key by reverse engineering.

Another approach that was used is the gradle file approach. Here we add the API key in the gradle.properties file:

#gradle.properties file

#API Key
MyAwesomeKey = "YOUR_AWESOME_API_KEY_HERE"

After that, import the API key as buildConfigField in the build.gradle file.

buildTypes {
    debug {
        buildConfigField 'String', "ApiKey", MyAwesomeKey
        resValue 'string', "api_key", MyAwesomeKey
    }
    ...
}

But still anyone can get the API key by reverse engineering your code.

So, both methods failed to secure the API keys. We need a certain concrete method that can be used so that even after reverse-engineering the code, no one can get the desired API key. Let’s find a solution.

Proposed Solution

We have discussed the problem in the previous section. So, what should we do so that even after reverse engineering no one can get the API key?

One solution to the above problem can be the use of native language in our application code. In the native languages like in C or C++, the Android Native Development Kit(NDK) compiles the code to .so file. The benefit with this .so file is that it contains binary numbers i.e. in the form of 0s and 1s. So, even after reverse engineering, you will get 0s and 1s and it is very difficult to identify what is written in the form of 0s and 1s.

No doubt, there are methods to get the code from 0s and 1s also, but as I said earlier, we are just providing some extra security layers to our code.

How to do this in Android? (three ways)

In Android, we have the support of Native languages with the help of the Android Native Development Kit (NDK). Also, there is JNI(Java Native Interface) in Android. JNI defines a way for the bytecode that is generated from the Java or Kotlin code to interact with the native code i.e. code written in C or C++.

So, with the help of Android NDK, we can secure the API keys. Based on the versions of Android Studio, we have three approaches to secure our API keys:

  1. Using the Native C++ template
  2. Using CMake
  3. Using ndk-build

But before moving on to these approaches, there are some prerequisites.

Prerequisites

In this blog, you will be learning three different ways of securing API keys using the Android NDK. So, before moving onto that, you have to download some tools. Follow the below steps:

  1. In your Android Studio, click on Tools > SDK Manager > SDK Tools.
  2. Select LLBD, NDK, and CMake.
  3. Click on Apply and after downloading and installing click on OK.

LLBD: It is used by Android Studio to debug the native code present in your project.

NDK: Native Development Kit(NDK) is used to code in C and C++ i.e. native languages for Android.

CMake: It is an open-source system that manages the build process in an operating system and a compiler-independent manner.

Now, we done with downloading the tools, let’s quickly move on to the approaches of securing the API keys.

Using the Native C++ template

In the latest versions of Android Studio, we have support for native code i.e. for the C and C++. Follow the below steps to add Native C++ in your project:

Step1: Create a new project in Android studio with Native C++ template.

Step2: Add the details of the project and click on Next.

Step3: Keep the settings to default and click on Finish.

Step4: You can find that by default you will be having native-lib.cpp file and the CMakeLists.txt file added to your project under the cpp directory.

The native-lib.cpp file is the file that contains your API keys. You can add your API keys in the file as below:

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_mindorks_myapplication_APIKeyLibrary_getAPIKey(JNIEnv* env, jobject /* this */) {
    std::string api_key = "YOUR_AWESOME_API_KEY_GOES_HERE";
    return env->NewStringUTF(api_key.c_str());
}

Following is the description of the above code:

  • Here, you have to follow the combination of PackageName_ActivityName_MethodName.
  • In the above example, com_mindorks_myapplication is the package name, APIKeyLibrary is the file name and getAPIKey is the method that is used to get the API keys from the native code.
  • You can directly return the API key but normally people use some encryption technique to encrypt the API key. So, the NewStringUTF() method is used to do the UTF-8 encoding.

Step5: 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")
}

Step6: Now, declare a Kotlin external function with the same name as used in the native code.

external fun getAPIKey(): String

Step7: Finally, you can get the API key by calling:

APIKeyLibrary.getAPIKey()

That’s all! You have secured your API key :)

So far so good

If you are having an older version of Android Studio that doesn’t have the support of the Native C++ template. Then you can make use of CMake. The difference between these two approaches is that in the CMake approach you have to add the files manually while in the previous approach everything is added by default.

Using CMake

Apart from using the Native C++ template, we can use the CMake to secure the API keys i.e we can add the files manually to our project.

CMake is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler environment of your choice.

For more information on CMake, you can visit its official website.

Following are steps that can be followed to secure API keys using CMake:

Step1: Install the required tools that are mentioned in the prerequisite part of this blog.

Step2: Under the app/src/main directory, create a directory named cpp.

Step3: In the cpp directory, create a native file where you want to store your API keys. So, create a file named api-keys.cpp and add the below code:

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_mindorks_myapplication_APIKeyLibrary_getAPIKey(JNIEnv* env, jobject /* this */) {
    std::string api_key = "YOUR_AWESOME_API_KEY_GOES_HERE";
    return env->NewStringUTF(api_key.c_str());
}

Step4: In your app/ directory, you need to add one text file named CMakeLists.txt file. It is a CMake build script. Add the below content into it:

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
api-keys

# Sets the library as a shared library.
        SHARED

# Provides a relative path to your source file(s).
src/main/cpp/api-keys.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
        log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
native-lib

# Links the target library to the log library
# included in the NDK.
        ${log-lib} )

Step5: Now, you have to specify the path of your CMakeLists file in the build.gradle file. So, add the below code in build.gradle file:

android {
    ...
    defaultConfig {
        ...
    }
    buildTypes {
        ...
    }
    externalNativeBuild {
        cmake {
            path 'CMakeLists.txt'
        }
    }
}

Now, to access the API keys from your Activity or file, you can follow the same steps(Steps 5, 6, and 7) as followed in the previous approach i.e. the Native C++ template approach.

If your Android Studio version doesn’t have the support of CMake then also you can use the NDK to secure your API keys. Here, you can use the ndk-build process. Let’s see how to do this.

Using ndk-build

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 is present inside the jni/ directory. It describes your sources and shared libraries to the build system. The basic purpose of the Android.mk file is to define project-wide settings that are not defined by the build system or by Application.mk or by environment variables. Also, the syntax of the Android.mk file allows you to group your sources into 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    := api-keys
LOCAL_SRC_FILES := api-keys.c

include $(BUILD_SHARED_LIBRARY)

Following is 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 api-keys.c and add the below code into it:

#include <jni.h>

//For first API key
JNIEXPORT jstring JNICALL
Java_com_mindorks_myapplication_APIKeyLibrary_getAPIKey(JNIEnv *env, jobject instance) {

    return (*env)->  NewStringUTF(env, "YOUR_AWESOME_API_GOES_HERE");

}

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'
        }
    }
}

Now, to access the API keys from your Activity or file, you can follow the same steps(Steps 5, 6, and 7) as followed in the previous approach i.e. the Native C++ template approach.

Closing Note

In this blog, we learned how to secure our API keys with the help of the Android Native Development Kit. We saw three methods of doing so i.e. the ndk-build method, the CMake method and the easiest one i.e. using the native c++ template in our project.

Hope you learned something new today.

Do share this blog with your fellow developers to spread the knowledge. You can read more blogs on Android on our blogging website.

Apply Now: MindOrks Android Online Course and Learn Advanced Android

Happy Learning :)

Team MindOrks!