This project demonstrates how to store an API key inside native C++ code (NDK) instead of placing it
directly in Kotlin. The goal is not to achieve perfect security, but to make sensitive values harder
to extract than they would be in plain .dex files.
The app loads an API key from a native .so library, decrypts it in C++, and exposes it to Kotlin
through JNI.
- Store an encrypted API key in native code
- Decrypt the key inside C++
- Access the decrypted value in Kotlin via JNI
- Uses:
- Kotlin
- C++ (NDK)
- CMake
- JNI
app/
├── src/main/java/com/example/ndkstore/
│ ├── MainActivity.kt
│ └── NativeLib.kt
├── src/main/cpp/
│ └── native-lib.cpp
├── CMakeLists.txt
└── build.gradle
NativeLib.kt
Loads the native library and defines the external function used to fetch the API key.
native-lib.cpp
Stores the encrypted key as a byte array, decrypts it using XOR, and returns it as a jstring.
CMakeLists.txt
Defines how the C++ source is compiled into a shared library.
MainActivity.kt
Calls the native method and shows the resulting key on the screen.
- The API key is stored as XOR-encrypted bytes in
native-lib.cpp. - C++ decrypts the bytes back into a readable string at runtime.
- JNI sends the decrypted value back to Kotlin.
- Kotlin loads the native library using
System.loadLibrary("native-lib"). - The app displays the key to confirm everything works.
This technique does not fully secure secrets, but it raises the difficulty for anyone trying to read or modify them.
- Choose your new key, for example: MY_NEW_SECRET_123
- Generate encrypted bytes using Python:
plaintext = "MY_NEW_SECRET_123"
xor = 0x5A
encrypted = [ord(c) ^ xor for c in plaintext]
print(encrypted)- Update the C++ array:
static const unsigned char encrypted[] = {
/* paste generated values here */
};
- Rebuild the project. Your new key is now embedded in the native library.
Aside from API keys, developers often store:
- Feature unlock flags
- Hidden developer options
- Internal configuration strings
- Small anti-tamper checks
- Root or signature detection logic
- Proprietary formulas or lookup tables
- Lightweight license validation data
These are not impossible to extract, but placing them in native code makes reverse engineering significantly harder.
- The NDK improves obfuscation, not absolute protection.
- Any client-side secret can be extracted with enough effort.
- For sensitive production keys, always use a backend server that issues short-lived tokens.