Blog Infos
Author
Published
Topics
Published
Topics

Photo by Jason D on Unsplash

 

There are many ways to secure your key when building an APK. But for now, I want to explain one way to secure your key: store it in NDK and double-secure with an encrypted key.

I believe that this is not a 100% guarantee that your key is secure. But at least make it hard for an attacker to get your key or any sensitive data from your APK.

Read this documentation about NDK from Google.

Add C++ to Module

In Android Studio, you can easily add a C++ module to your app module with a right-click on your app module and a click on Add C++ to Module. And also, you can do it by double-clicking the shift button on your keyboard and typing Add C++ to Module.

Once you’re done, you can create a CMakeLists.txt and put it under app/src/main/cpp.

Finally, from these processes, it will do several things:

  • Generate CMakeLists.txt. We can leave it as is for CMakeLists.txt.
# CMakeLists.txt

# 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.18.1)

# Declares and names the project.

project("encryptedndk")

# 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.
             encryptedndk

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             encryptedndk.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.
                       encryptedndk

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
  • Generate encryptedndk.cpp. In this file, we will add some code to put our API_TOKEN here.
// encryptedndk.cpp

#include <jni.h>

extern "C" JNIEXPORT jstring JNICALL
// Java_{package_name}_{class_name}_{method_name}
Java_com_adefruandta_encryptedndk_EncryptedNdk_apiTokenNative(JNIEnv *env, jobject object) {
    // API_TOKEN is a constant that will be passed from gradle
    return env->NewStringUTF(API_TOKEN);
}
  • Modify your app/build.gradle
android {
    ...

    defaultConfig {
        ...

        externalNativeBuild {
            cmake {
                // This is the way we pass our API_TOKEN from gradle to cpp
                cppFlags '-DAPI_TOKEN=\\\"This_is_API_TOKEN_from_native\\\"'
            }
        }
    }

    // This section is auto updated from Add C++ to module process
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.18.1'
        }
    }
}

Just try to sync and build the project. Make sure there is no error.

Linking cpp to kotlin class

After we have a cpp file to store our API_TOKEN, we can create a class with the same package name, class name, and method name as in your cpp file.

package com.adefruandta.encryptedndk

object EncryptedNdk {
    init {
        System.loadLibrary("encryptedndk");
    }

    external fun apiTokenNative(): String
}

// or if you prefer, use class instead of object

class EncryptedNdk {
    
    companion object {
        init {
            System.loadLibrary("encryptedndk");
        }
    }

    external fun apiTokenNative(): String
}

Make sure there is an icon on the left side of your method apiTokenNative(). It means you’ve already succeeded in connecting your cpp to your Kotlin class.

After that, you can do some testing by printing it into your activity or showing the toast.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Toast.makeText(this, EncryptedNdk.apiTokenNative(), Toast.LENGTH_LONG).show()
    }
}

OUR VIDEO RECOMMENDATION

, , ,

Unlocking Mobile App Security- Key Insights for Navigating a Rapidly Evolving Threat Landscape

In today’s digital age, mobile apps have evolved into multi-faceted platforms that blend communication, collaboration, and commerce.
Watch Video

Unlocking Mobile App Security- Key Insights for Navigating a Rapidly Evolving Threat Landscape

Krishna Vishnubhotla
Product Strategy
Zimperium

Unlocking Mobile App Security- Key Insights for Navigating a Rapidly Evolving Threat Landscape

Krishna Vishnubhot ...
Product Strategy
Zimperium

Unlocking Mobile App Security- Key Insights for Navigating a Rapidly Evolving Threat Landscape

Krishna Vishnubh ...
Product Strategy
Zimperium

Jobs

No results found.

All done, you’ve already successfully put your first API_TOKEN into NDK.

Encrypt API_TOKEN

To have an encrypted API_TOKEN, you need to have an encryption function that you will use before passing it into cppFlags in your Gradle script. Create encryptor.gradle in the root project.

import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

def algorithm = "AES/CBC/PKCS5Padding"
def iv = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] as byte[]
def ivSpec = new IvParameterSpec(iv)

ext.encrypt = { text, key ->
    def cipher = Cipher.getInstance(algorithm)
    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.bytes, "AES"), ivSpec)
    def cipherText = cipher.doFinal(text.bytes)
    return cipherText.encodeBase64()
}

More information on the algorithm can be found here. You can change it to something suitable for your project. Different algorithms have different requirements for your key. In the preceding example, use the AES/CBC/PKCS5Padding algorithm to generate a key of 16 characters (or 16 bytes).

After that, you can use it in your app/build.gradle.

apply from: '../encryptor.gradle'

android {
    ...

    defaultConfig {
        ...

        externalNativeBuild {
            cmake {
                // encrypt when passing to cppFlags
                cppFlags '-DAPI_TOKEN=\\\"' + encrypt("This_is_API_TOKEN_from_native", "1234567890123456") + '\\\"'
            }
        }
    }
}

Just try to run it and see the result. The API_TOKEN will be shown encrypted.

Decrypt API_TOKEN

After you successfully encrypt your API_TOKEN, you need to decrypt it to get the actual value of your API_TOKEN. So you need to have a function on the EncryptedNdk class to decrypt apiTokenNative().

object EncryptedNdk {
    ...

    external fun apiTokenNative(): String

    fun apiToken(): String = decrypt(apiTokenNative())

    // region Decryptor
    // The algorithm should be the same with encryptor
    private const val algorithm = "AES/CBC/PKCS5Padding"
    private val cipher = Cipher.getInstance(algorithm)
    private val iv = ByteArray(16)
    private val ivSpec = IvParameterSpec(iv)
    private val keySpec = SecretKeySpec("1234567890123456".toByteArray(), "AES")
    
    private fun decrypt(
        text: String
    ): String {
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
        val plainText = cipher.doFinal(Base64.decode(text, Base64.DEFAULT))
        return String(plainText)
    }
    // endregion
}

And finally, change from EncryptedNdk.apiTokenNative() to EncryptedNdk.apiToken() on MainActivity.kt.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Toast.makeText(this, EncryptedNdk.apiToken(), Toast.LENGTH_LONG).show()
    }
}

It should show the actual API_TOKEN on Toast.

Proguard rule

Because we write the name of the package, the class name, and the method name on the cpp file, we need a proguard rule to keep the package name, the class name, and the method name for your EncryptedNdk class.

-keep class com.adefruandta.encryptedndk.EncryptedNdk {
    native <methods>;
}
Final touch

Because you have C++ in your module, when you build your project, it will generate a cxx folder in your module. So you need to ignore it from git. And also, you can add a custom Gradle task to delete the cxx folder when running the clean task.

// app/build.gradle

project.task("cleanCxx") {
    delete '.cxx'
}
project.tasks.findByName("clean").finalizedBy("cleanCxx")

I hope this article will help you protect and secure your keys, tokens, or any sensitive data when you store it in your APK. See the repository below for a full example.

GitHub – adef145/EncryptedNdk

Keep safe and stay healthy.

Clap if you like, and more claps if you want to.

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
This is the accompanying blog post for my recent Droidcon Berlin 2023 talk “How…
READ MORE
blog
When developing Android Apps, we rely on many third-party libraries. It could be for…
READ MORE
blog
In modern terms, the term “SSL”(Secure Sockets Layer) often refers to “TLS” (Transport Layer…
READ MORE
blog

Android 12 review for developers

The Google I/O’21 conference took place, and we learned absolutely everything about the new…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu