As you may know, an apk file is actually just a zip archive, so you can try to rename (or simply force your decompressing tool to open the apk file) the file to appname.apk.zip
and extract it with any zip utility. In this example we are going to build an application that uses React (not react native) and webpack to generate all the js code in a single file app.js
. Then we build the app using cordova build android --release
and we see the content of the apk generated file (build or debug) in project/platforms/android/build/outputs/apk
with a zip utility (in this case we are going to use WinRAR):
And if you try to edit some of the extracted files (our app.js
), then it will contain:
Do you see what I see ? The source code of your application, readable for anyone that knows how to open an APK with a zip utility. Although our code is minified for production (not made by cordova but us), the code still visible, it can be pretty printed and therefore readable. This ain't so good (obviously) as your code may expose security flaws (if there's any) in features like the login into your app, payments process etc.
As a lot of developers say, and you probably need to know, there's no 100% security against the reverse engineering. You can do more to protect the code of your app though, but in fact it will be more tough mostly for the average user, who just google for "How to hack an APK". If somebody really wants to hack your app, it can be hacked, sooner or later (unless your app communicates a lot with the server and most of the functionalities aren't on the device).
In this article you will learn how to protect (or at least increase the protection level) of the source code of your application in Cordova.
1. Install the encryption plugin
We are going to use the cordova-plugin-crypt-file plugin to encrypt all the files inside the www
folder of your project (the files that are included in the generated apk). It can be used on any cordova project (even if it uses Crosswalk) and although this tutorial is meant to be used only in Android, the plugin itself supports iOS platforms too.
Install the plugin using the following command in your terminal (once you're located in your project folder):
cordova plugin add cordova-plugin-crypt-file
Once the plugin is installed it will start working automatically and on every build, it will encrypt the files.
How the encryption works ?
This plugin uses the AES/CBC/PKCS5Padding encryption algorithm to encrypt the files. The files are encrypted by the plugin using a random generated encryption key and an initialization vector(IV) during the compilation of your application (that means that the original files inside your project won't be modified, only the files of the generated APK). As expected, the files will be decrypted everytime your application inits on the installed device.
2. Customizing which files should be encrypted
As previously mentioned, the files are encrypted and there's no way to decrypt them with Javascript. In case you need a file not to be encrypted, you can customize which files you want to encrypt with a custom regular expression. However you will need to specify this in a file of the plugin. Start by opening the plugin.xml
located in the /project/plugins/cordova-plugin-crypt-file
and modify the cryptfiles tag.
By default the plugin includes the following regex that encrypts all css,htm,html and js files inside the www directory:
<cryptfiles>
<include>
<file regex="\.(htm|html|js|css)$" />
</include>
<exclude>
</exclude>
</cryptfiles>
Simply customize the regex to specify which files will be encrypted e.g the following tag will compress only html, htm, js but not the css files and will exclude a single JS file with the name example
:
<cryptfiles>
<include>
<file regex="\.(htm|html|js)$" />
</include>
<exclude>
<file regex="example.js" />
</exclude>
</cryptfiles>
Save the changes once they're made.
3. Verify if files were encrypted
Now after the installation of the plugin (and optional customization of files to encrypt) you can verify if the plugin is working or not. Repeat the same process mentioned at the beginning of the article. Build your application (either debug or release mode) and use a zip utility to see the content of the www
folder in your apk (assets/www
):
As you can see, the structure of the files remains the same. Finally, edit any of the files (as long it isn't excluded from the encryption) with a code editor and you will see that the content of the file is encrypted:
It seems unreadable now, isn't?. The installed plugin will decrypt the files during the runtime, your app will run as expected and your source code has a higher level of protection.
Extra security
You can use ProGuard in your generated APK with Cordova. ProGuard optimizes the bytecode, removes unused code instructions, and obfuscates the remaining classes, fields, and methods with short names. The obfuscated code makes your APK difficult to reverse engineer, which is especially valuable when your app uses security-sensitive features, such as licensing verification.
Create the proguard-rules.pro
file inside the /project/platforms/android
folder with the following content:
# By default, the flags in this file are appended to flags specified
# in /usr/share/android-studio/data/sdk/tools/proguard/proguard-android.txt
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
##---------------Begin: proguard configuration common for all Android apps ----------
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-dontpreverify
-verbose
-dump class_files.txt
-printseeds seeds.txt
-printusage unused.txt
-printmapping mapping.txt
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-allowaccessmodification
-keepattributes *Annotation*
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable
-repackageclasses ''
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
-dontnote com.android.vending.licensing.ILicensingService
# Explicitly preserve all serialization members. The Serializable interface
# is only a marker interface, so it wouldn't save them.
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# Preserve all native method names and the names of their classes.
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# Preserve static fields of inner classes of R classes that might be accessed
# through introspection.
-keepclassmembers class **.R$* {
public static <fields>;
}
# Preserve the special static methods that are required in all enumeration classes.
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep public class * {
public protected *;
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
##---------------End: proguard configuration common for all Android apps ----------
#---------------Begin: proguard configuration for support library ----------
-keep class android.support.v4.app.** { *; }
-keep interface android.support.v4.app.** { *; }
-keep class com.actionbarsherlock.** { *; }
-keep interface com.actionbarsherlock.** { *; }
# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
-dontwarn android.support.**
-dontwarn com.google.ads.**
##---------------End: proguard configuration for Gson ----------
##---------------Begin: proguard configuration for Gson ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-keep class sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.example.model.** { *; }
##---------------End: proguard configuration for Gson ----------
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
Then edit the build.gradle
file (inside the /project/platforms/android
or create a Cordova plugin that adds that to the build file automatically):
android {
...
buildTypes {
debug {
minifyEnabled true
useProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
...
}
As a final step build your apk in release mode using cordova build android --release
and you're ready to go.
ProGuard shrinks your code to make it a little harder to read (minified), which could slow the reverse engineering process. Although ProGuard doesn't obfuscate string constants, it is more specialized in closed-source sibling for Android, DexGuard and provides additional application protection techniques, like string encryption and class encryption.
If you know a cordova developer, that probably doesn't know about this problem, please share this article. Happy coding !