Hiding sensitive data using NDK in an Android application
As we progress through the Industry 4.0 Technology era, there are a lot of ways that an attacker could get around the system that we built and take advantage of the loopholes that we may have accidentally left. In the context of an Android application, there are many ways that we can use to improve our system’s security.
In this post, we’re going to walk through how can we use the NDK(Native Development Kit) to hide those sensitive data.
But first, what exactly is NDK?
The Native Development Kit (NDK) is a set of tools that allows you to use C and C++ code with Android, and provides platform libraries you can use to manage native activities and access physical device components, such as sensors and touch input
In short, NDK allows us to use C/C++ codes and let our app interact with them.
Why do we need NDK?
Oftentimes, a lot of sensitive data are stored locally within the code. Here is a couple of example of them:
- API Keys from third-party libraries
- URLs to make server API calls. Example- Base URLs to our private server
There are many ways to store these data, and often times those methods are not ideal. You may have tried to:
- Store them as string constants in a Kotlin/Java class
- Hardcode them wherever you need them (ex:
Retrofit.Builder().baseUrl("https://my.backend.server")
) - Store them in
Build.Config
file
These solutions work just fine at the expense of the application’s security. The attacker could decompile your .apk
and easily read these data
As we can see from the example above, the BASE_URL
‘s value can be easily compromised with a simple few clicks of a button
So how does NDK help us with this problem exactly?
We store our sensitive data in a C/C++ class, then we call those classes using the JNI(Java Native Interface) from our Kotlin/Java classes.
The codes that we wrote in the C/C++ class are generated as .so
files by the NDK that are much harder to decrypt than a regular Java/Kotlin class. Although reverse engineering the .so
files is technically not impossible, it would be very hard to do. Thus, adding a layer of complexity for the attackers.
Let’s jump into it!
First thing first, you need to install the CMake
and the NDK
itself. You can easily do it by going through the menu Tools > SDK Manager > SDK Tools
. You will find these two options, simply check them and wait for them to get installed.
There is another way to install the NDK, which is by manually downloading the NDK from the official website, then adding the folder’s path to your project’s local.properties
Next, create a cpp
folder on whichever android module you need to access the file from
Then, you need to create a .cpp
file with the name native-lib
(it can be whatever you want). Next, you can copaste the code below.
Now, there are a few things that you need to take note of when you name the function. To make it available in Java/Kotlin code, the format of the function name is Java_package_name_class_name_function_name
Java
if you use Kotlin folder instead, then you need to rename it toKotlin
class_name
is the name of the class of which the native function will be called frompackage_name
is the name of the application’s package name where the class resides.function_name
is the name of the function which will be called from Kotlin/Java class.
Here is an example
in this case, then my function will be: Java_com_tanakayu_mini_projects_di_NdkModule_getBaseUrlFromNative
Later on, we will access our C++ function using the getBaseUrlFromNative
function from Java/Kotlin file. The name of the function itself can be whatever you want, but you will access them with whatever you put inside the .cpp
file.
Next, we need to create a CMakeLists.txt
(the name of the file has to be exact) file inside the cpp
folder. Then, you can just copaste this code below into your CMakeLists.txt
file.
Basically what the add_library
command does is that we tell CMake to construct a native library with the name of “native-lib” from the .cpp file that we specify in the third parameter native-lib.cpp
And lastly, we also need to specify externalNativeBuild
in the module’s build.gradle
file.
And that’s it! We’re finished with the setup for now. The next step will be calling the function from our Kotlin/Java class.
Calling our native method
Finally, inside the file that we target, we call the System.loadLibrary("native-lib")
inside the init
block (static
if you’re using Java)
And that’s it! We are finished. This would dramatically increase our app’s security as when an attacker tries to decompile our app it will be very hard for them to find the data that we store within our C++ file.
What’s Next?
Oftentimes, when we’re working for a big company there would be multiple values that are considered sensitive data. In this blog, we are taking the assumption that the application only has one baseUrl
for its interaction with the backend server. But what if it has more than one?? for example, we have separate baseUrls for each deployment environment, which are usually dev, staging, and production.
Well, we would have to refactor our cpp
class and make it return a List
or Map
instead of a single String
. In theory, it doesn’t sound so complicated, but in reality, it’s not as straightforward, which I will share in the next blog post.