Android - How To Protect API Keys From Decompiled APK
- Background Knowledge:
- Some Libraries:
SOURCE (untested), SOURCE (can't add to gradle), SOURCE
There are many ways to do this, some common way is to add the keys/secret to properties
or xml
resource files and read it via Gradle or Resource. Rememer to gitignore
from version control so we don't push it to remote repo.
However, these methods are not enough to protect against decompiled APK so another method is to put those keys/ secret in native code which will make it harder for attacker to reverse engineer our app.
Remember, this method is still not enough, a combination of ProGuard/ DexGuard, encrypt/ decrypt keys/ secret in native code, fetching keys/ secret from server, using Android Keystore, etc. is recommended to fully protect our app.
In your Android Studio, click on Tools > SDK Manager > SDK Tools.
Select LLBD
, NDK
, and CMake
.
Click on Apply and after downloading and installing click on OK.
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_example_weatherforecast_repository_ForecastRepository_getAppID(
JNIEnv* env,
jobject /* this */) {
std::string app_id = "60c6fbeb4b93ac653c492ba806fc346d";
return env->NewStringUTF(app_id.c_str());
}
the format is:
Package_ClassName_MethodName
Step5: To call this message in any class, do this:
class ForecastRepository(private val database: ForecastDatabase) : BaseRepository() {
init {
System.loadLibrary("native-lib")
}
private external fun getAppID(): String
...
fun getSomethingFromNetwork() {
...
response = apiService.getDailyForecastForCity(
input,
10,
getAppID(),
"Metric"
)
...
}
}
To call it from companion object
add @JvmStatic
to the function SOURCE:
class ForecastDatabase : RoomDatabase() {
companion object {
init {
System.loadLibrary("native-lib")
}
@JvmStatic
private external fun getDBPass(): String
fun getDatabase(context: Context): ForecastDatabase {
....
val pass = getDBPass()
}
}
}
To verify if the compiled APK have our native libs, do this: SOURCE
- Select
Build > Build Bundles(s) / APK(s) > Build APK(s)
- Select
Build > Analyze APK
. - Select the APK from the
app/build/outputs/apk/
directory and click OK. - As shown in figure 3, you can see
libnative-lib.so
in the APK Analyzer window underlib/<ABI>/
.
For other method using CMake
or ndk-build
see the Source Article.