由于使用NDK开发,可以中间的数据放入到so中,这样是的关键数据更安全。因为破解原生代码相对来说太容易,而so文件相对来说门槛较高。
我们本篇就是从安全角度来使用NDK开发,将重要的数据放入NDK中,同时将重要的加密也放入到NDK开发,这样在一定程度上可以保证APP应用的安全。
校验APK安装包信息
获取应用包名
在上篇文章中,我们可以通过NDK的方法来获取APK的包名。
1 2 3 4 5 6 7 8 9
| extern "C" JNIEXPORT jstring JNICALL Java_me_cyning_helloworld_NativeUtils_getPackageName( JNIEnv *env, jclass clazz, jobject instance) { jclass nativeClass = env->GetObjectClass(instance); jmethodID jmethodID1 = env->GetMethodID(nativeClass, "getPackageName", "()Ljava/lang/String;"); jstring packageName = static_cast<jstring>(env->CallObjectMethod(instance, jmethodID1)); return packageName; }
|
Java层调用:
1
| public static native String getPackageName(Context context);
|
但是在实际的应用中,能不能不传Context来获取到全文的Context呢?答案是可以的!
1 2 3
| Class<?> activityThreadClzz = Class.forName("android.app.ActivityThread"); Method currentApplication = activityThreadClzz.getMethod("currentApplication"); Application application = (Application) currentApplication.invoke(null);
|
我们可以参考:ActivityThread。由于currentApplication
是个静态方法,可以通过反射获取到一个Application对象。
结合之前的代码我们可以合并如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| static jobject getApplication(JNIEnv *env) { jobject application = NULL; jclass activity_thread_clz = env->FindClass("android/app/ActivityThread"); if (activity_thread_clz != NULL) { jmethodID currentApplication = env->GetStaticMethodID( activity_thread_clz, "currentApplication", "()Landroid/app/Application;");
if (currentApplication != NULL) { application = env->CallStaticObjectMethod(activity_thread_clz, currentApplication); } else { LOGE("Cannot find method: currentApplication() in ActivityThread."); } env->DeleteLocalRef(activity_thread_clz); } else { LOGE("Cannot find class: android.app.ActivityThread"); }
return application; }
static jstring getPackageName( JNIEnv *env,jobject instance ) { jclass nativeClass = env->GetObjectClass(instance); jmethodID jmethodID1 = env->GetMethodID(nativeClass, "getPackageName", "()Ljava/lang/String;"); jstring packageName = static_cast<jstring>(env->CallObjectMethod(instance, jmethodID1)); return packageName; }
extern "C" JNIEXPORT jstring JNICALL Java_me_cyning_helloworld_NativeUtils_getPackageName( JNIEnv *env, jclass clazz) { return getPackageName(env, getApplication(env)); }
|
但是得到包名就安全了么,NO!有些”居心不良”的人可能会改我的包签名,好了那怎么就校验下包签名,不合法的APP让它直接GG。
怎么获取包签名信息呢?
获取应用的签名信息
怎么获取一个APP的签名信息呢?还是和获取包名一样,我们需要拿到在java上的实现。
1 2 3 4 5
| PackageManager pm = context.getPackageManager(); PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES); Signature[] signatures = pi.signatures; Signature signature0 = signatures[0]; String string = signature0.toCharsString();
|
用NDK怎么实现呢?
若是你不知道C怎么调用java,这是个很好的学习范例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| extern "C" JNIEXPORT jint JNICALL Java_me_cyning_helloworld_NativeUtils_getSignature(JNIEnv *env, jclass clazz) {
jobject context_obj = getApplication(env); jclass context_clazz = env->GetObjectClass(context_obj); jstring packageName = getPackageName(env, context_obj);
jmethodID getPackageManagerMethod = env->GetMethodID(context_clazz, "getPackageManager", "()Landroid/content/pm/PackageManager;"); jobject pm_obj = env->CallObjectMethod(context_obj, getPackageManagerMethod);
jclass pm_clazz = env->GetObjectClass(pm_obj); jmethodID getPackageInfoMethod = env->GetMethodID(pm_clazz, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"); jobject packageInfo_obj = env->CallObjectMethod(pm_obj, getPackageInfoMethod, packageName, 64);
jclass pi_clazz = env->GetObjectClass(packageInfo_obj); jfieldID signatures_field = env->GetFieldID(pi_clazz, "signatures", "[Landroid/content/pm/Signature;");
jobject signatures = env->GetObjectField(packageInfo_obj, signatures_field); jobjectArray signatures_array = (jobjectArray) signatures;
jobject signature0 = env->GetObjectArrayElement(signatures_array, 0);
jclass signature_clazz = env->GetObjectClass(signature0); jmethodID toCharsStringMethod = env->GetMethodID(signature_clazz, "toCharsString", "()Ljava/lang/String;"); jstring signature_string = (jstring)env->CallObjectMethod(signature0, toCharsStringMethod);
env->DeleteLocalRef(context_obj); env->DeleteLocalRef(context_clazz); env->DeleteLocalRef(packageName); env->DeleteLocalRef(pm_obj); env->DeleteLocalRef(pm_clazz); env->DeleteLocalRef(packageInfo_obj); env->DeleteLocalRef(pi_clazz); env->DeleteLocalRef(signatures); env->DeleteLocalRef(signature0); env->DeleteLocalRef(signatures_array); env->DeleteLocalRef(signature_clazz);
return signature_string;
|
实现的对应代码:
这样就可以将获取的签名和本地的签名比较:
1 2 3 4 5 6 7 8 9
| const char *sign = env->GetStringUTFChars(signature_string, NULL); int result = strcmp(sign, "app的签名"); env->ReleaseStringUTFChars(signature_string, sign); env->DeleteLocalRef(signature_string); if (result == 0) { return JNI_OK; } return JNI_ERR;
|
重写JNI_OnLoad
:
1 2 3 4 5 6 7 8 9 10 11
| jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env = NULL; if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) { return JNI_ERR; } if (verifySign(env) == JNI_OK) { return JNI_VERSION_1_4; } LOGE("签名不一致!"); return JNI_ERR; }
|
这样就会在System.loadLibrary("native-lib");
时,就会调用JNI_OnLoad
,当签名不一致时就会直接导致崩溃。
加密
当然了,我们也可以在NDK层实现加密,如网络请求时,将参数加密等,这样相对来说:
安全 底层实现的加密不易破解
高效 c实现的加密相对来说速度快,效率高
具体的代码可以参考这篇文章,不过可以根据需求来自己定义加密算法。