Hiding sensitive data in android app

Priya Sindkar
Novumlogic
Published in
5 min readOct 3, 2020
Photo by Markus Spiske on Unsplash

How many times do we worry about our sensitive data shipped with the apk without any security?

In this blog post I am going to talk about hiding sensitive data in your android app. I will explore this topic by answering the following questions-What, Why, and How?

The What?

What is it that we need to hide in our code? There are many examples of sensitive string data that needs hiding-

  1. API keys from third party libraries. Example- Google Maps API key
  2. URLs to make server API calls. Example- Base URLs to our private server
  3. Default login credentials (if your app allows some kind of demo login to showcase various features before user can subscribe to them). Example- Username and password

The Why?

Why do we need to hide the data when android already uses proguard to protect the classes?

There are many ways in which you can store sensitive string constants in your android application to use in webservice calls to your server like:

  1. Store as string constants in some .kt/.java file
  2. Store as string resource in strings.xml
  3. Store inside Build.Config from android gradle plugin
  4. Directly use the strings as literals in the code

However, if your application is reverse engineered (decompiled), the string constants can be easily intercepted and misused.

Strings stored using above mechanisms can be intercepted in a decompiled application like so-

For the purpose of brevity, I would be taking a base URL for interacting with a server as an example throughout the post. Let’s consider following the literal as an example- https://domainname/functiontype/.

  1. The URL intercepted from a decompiled constants class-
/* compiled from: AppConstants.kt */
public final class AppConstants {
@NotNull
private static final String BASE_URL = "https://domainname/functiontype/";
public static final Companion Companion = new Companion();

@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0002\b\u0003\b†\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002R\u0014\u0010\u0003\u001a\u00020\u0004X†D¢\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006¨\u0006\u0007"}, d2 = {"Lcom/novumlogic/ndktest/AppConstants$Companion;", "", "()V", "BASE_URL", "", "getBASE_URL", "()Ljava/lang/String;", "app_debug"}, k = 1, mv = {1, 1, 13})
/* compiled from: AppConstants.kt */
public static final class Companion {
private Companion() {
}

@NotNull
public final String getBASE_URL() {
return AppConstants.BASE_URL;
}
}
}

2. The URL intercepted from decompiled strings.xml:

.....
<string name="abc_shareactionprovider_share_with_application">Share with %s</string>
<string name="abc_toolbar_collapse_description">Collapse</string>
<string name="app_name">Ndktest</string>
<string name="base_url">https://domainname/functiontype/</string>
<string name="search_menu_title">Search</string>
......

3. The URL intercepted from a decompiled BuildConfig class-

public final class BuildConfig {
public static final String APPLICATION_ID = "pacakge.name";
public static final String BUILD_TYPE = "debug";
public static final boolean DEBUG = Boolean.parseBoolean("true");
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
public static final String baseURL = "https://domainname/functiontype/";
}

4. The URL intercepted from decompiled from the code using it as a string literal-

@NotNull
public final Retrofit getInstance() {
Retrofit access$getINSTANCE$cp = ApiHelper.INSTANCE;
if (access$getINSTANCE$cp != null) {
return access$getINSTANCE$cp;
}
Retrofit access$getINSTANCE$cp2;
synchronized (this) {
access$getINSTANCE$cp2 = ApiHelper.INSTANCE;
if (access$getINSTANCE$cp2 == null) {
access$getINSTANCE$cp2 = new Builder().baseUrl("https://domainname/functiontype/").addConverterFactory(GsonConverterFactory.create()).build();
ApiHelper.INSTANCE = access$getINSTANCE$cp2;
}
}
Intrinsics.checkExpressionValueIsNotNull(access$getINSTANCE$cp2, "synchronized(this) {\n …ANCE = it }\n }");
return access$getINSTANCE$cp2;
}

Thus, the common ways to store strings in android application fail to provide any secure way to store untraceable string data.

The How?

So, how do we secure the strings? One way to secure the string constants without exposing it to hackers in the decompiled version is by using android NDK.

Using C/C++ JNI native code to hide sensitive string data with NDK and CMake

Note: This method does not guarantee full proof security against reverse engineering (Although the .so files are very hard to decompile, it is not impossible to do so) but it adds a layer of security.

The idea behind this way of securing sensitive data is to store the sensitive strings in C/C++ classes and fetching these string by function calling from Java/Kotlin classes.

The code for C/C++ classes are stored inside generated .so files which are harder to decrypt than the Java/Kotlin classes from the decompiled version.

Getting started

  1. Install CMake from Android SDK

Go to Tools -> SDK Manager -> SDK Tools -> Check CMake checkbox to install the latest CMake

Install Latest CMake

2. Install Android Native Development Toolkit (NDK)

You can tick NDK from the settings dialog above along with CMake in order to install NDK. Otherwise you can download NDK from official site. I already have NDK downloaded in my computer. I am therefore using the path to it’s folder in local.properties file.

local.properties file to store path to android NDK

3. Create a folder “cpp” under app/src/main. Create C/C++ file to store and access your base URL from- native-lib.cpp under cpp f`older.

native-lib.cpp

The above is a simple C++ function to return the base URL from a method named stringFromJNI().

Things to note

For making the function available in Java/Kotlin code, the format of the function name is Java_package_name_activity_name_function_name

  • activity_name is the name of the activity class inside which this native function is to be used.
  • package_name is the name of the application’s package name where the activity resides.
  • function_name is the name of the function which will be called from Kotlin/Java classes to fetch the URL.

4. Create CMake file named CMakeLists.txt (the file name has to be the same) in order to allow your app to build the native C++ code into a library.

The add_library CMake command is used to instruct CMake to build a native library with name specified (“native-lib”) from the native code from the path to .cpp file specified (src/main/cpp/native-lib.cpp).

Along with creating CMakeLists.txt, add the following to app’s build.gradle file in order to specify the path to CMakeLists.txt for CMake to compile and build the native code using NDK.

externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}

5. Next step is to make the native code available for use in Java/Kotlin activity. To enable this you must include the following in your Activity.

Note: For this example, I am using retrofit to illustrate the use of network calling.

Kotlin code-

Java code-

  • The loadLibrary() function is called from the Activity’s init block of companion object (Kotlin)/static init block (Java) because in this example we are required to access the native code in the launcher activity’s onCreate() method.
  • Note that the access modifiers- native in Kotlin and external in Java are used respectively in order to be able to access function from native.
  • Also note that the external function in Kotlin is annotated with @JvmStatic to enable static access to it. (i.e. direct usage of ApiHelper.baseUrlFromJni())

6. Build and Run the project.

That’s it!

Try to decompile your built apk and check if you can find the URL defined anywhere in the decompiled version. All you can see is libnative-lib.so files in the lib directory which is very hard to decipher.

--

--

Priya Sindkar
Novumlogic

Sr. Android Developer 💚 | Believer in neat code and clean architecture | Loves to read | A coder through and through 👩🏻‍💻