我们经常会有些敏感的信息需要客户端加解密,但android很容易被反编译,所以我们写在客户端里的密钥终究得不到安全,可能有人会想把加密方式写在C代码中,生成.so供APK使用,可是别人不关心你C里的代码,直接把你的so文件给拿去用就可以了,那么有没有一种安全的措施来加大难度呢,我说一下我们项目中如果解决客户端加密安全方案.
首先加密的代码仍然写在C代码中,至少C被反编译出来是汇编代码,加大阅读难度,另外一点就是如何让我们的.so文件只允许在我们自己的APK中使用,而不被别人拿去使用呢,这就需要签名验证这做一些文章,当程序调用.so时我们可以C代码中检查当前程序的签名是不是我们的,条件成立才进行加解密操作,当然签名泄露这个就另说了,毕竟没有百分百的安全,先来看看C代码 #include <string.h>
#include <jni.h> #include <stdio.h> #include <stdlib.h> #include <android/log.h>//LOG宏定义
#define LOG_TAG "JNI_SCRIPT" #define LOG_E(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, VA_ARGS)jstring JNICALL
Java_com_example_administrator_myapplication_Test_getScriptData( JNIEnv* env,jobject obj, jobject contextObj){ //根据传入的context对象获取getApplicationContext(),防止java中获取其它已安装APK的Context对象 jclass context_cls = (*env)->GetObjectClass(env,contextObj); jmethodID applicationContextMethod = (*env)->GetMethodID(env, context_cls, "getApplicationContext", "()Landroid/content/Context;"); jobject applicationContext = (*env)->CallObjectMethod(env, contextObj, applicationContextMethod); if (applicationContext == NULL) { LOG_E("context invalid!!"); }char *st = "com.example.administrator.myapplication"; //当前程序包名 //根据传入的context对象getPackageName jmethodID pkgName_method = (*env)->GetMethodID(env, context_cls, "getPackageName", "()Ljava/lang/String;"); jstring pkgName = (*env)->CallObjectMethod(env, applicationContext, pkgName_method); char *pkg = (*env)->GetStringUTFChars(env, pkgName, NULL); //对比 if (strcmp(st, pkg) != 0) { LOG_E("package name invalid!!"); return NULL; } // 获取PackageManager对象 jmethodID getPackageManager_method = (*env)->GetMethodID(env, context_cls, "getPackageManager", "()Landroid/content/pm/PackageManager;"); jobject packageManager = (*env)->CallObjectMethod(env, applicationContext, getPackageManager_method); // PackageManager class jclass packageManager_cls = (*env)->GetObjectClass(env, packageManager); // 得到 getPackageInfo 方法 jmethodID getPackageInfo_method = (*env)->GetMethodID(env, packageManager_cls, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"); // 获取PackageInfo类对象 jobject packageInfo = (*env)->CallObjectMethod(env, packageManager, getPackageInfo_method, pkgName, 64); // 获取PackageInfo class jclass packageInfo_cls = (*env)->GetObjectClass(env, packageInfo); jfieldID signatures_field = (*env)->GetFieldID(env, packageInfo_cls, "signatures", "[Landroid/content/pm/Signature;"); jobjectArray signatures = (*env)->GetObjectField(env, packageInfo, signatures_field); jobject signature = (*env)->GetObjectArrayElement(env, signatures, 0); jclass signature_cls = (*env)->GetObjectClass(env, signature); jmethodID hashcode_method = (*env)->GetMethodID(env, signature_cls, "hashCode", "()I"); int hashCode = (*env)->CallIntMethod(env, signature, hashcode_method); // 检测apk签名的hashCode值,然后进行对比 if (hashCode != -1370002482) { LOG_E("apk signature error,don not use this .so !!") } //上面条件都通过后可以进行加密算法处理,加密代码省略 //........ return "加密后的字符串";
}
java中代码 [java] view plain copy public class Test {static{ System.loadLibrary("mylib"); } public native String getScriptData(Context context);
}
上面的C的代码中我们从jni对API的反射来获取APK签名的hashCode值,第一步jni中接收Context对象,然后通过context对象获取getApplicationContext(),可能你会问我为什么要这么做,其实这一步是为了防止在java层传入恶意的context对象,打个比方,我们的程序叫A,恶意者的程序叫B,如果恶意者把A和B都安在手机中,那么在B程序中完全可以获取A程序的Context对象,这样一旦把A程序的Context对象传入jni中,我们的检测是通过的,恶意者是怎么能够在它的B程序中获取A程序的Context对象呢?下面一条语句就可以完成: [java] view plain copy Context thirdContext = createPackageContext("A程序的包名", Context.CONTEXT_IGNORE_SECURITY|Context.CONTEXT_INCLUDE_CODE);
是不是很容易,不过不用担心,虽然在B程序中通过上面代码获取A的Context,但我们只要用thirdContext.getApplicationContext()会返回null,所以我们在调用jni中第一步就是先获取一下ApplicationContext对象
第一步通过后,第二步获取当前程序的包名,进行对比,包名相同后接下来我们就要获取so的调用者程序的签名了,这一步就是安全的关键性所在,如果以上条件都通过了,逻辑就到了我们进行加解密的地方。
其实我在签名判断完后,又在安全性上做了一些小的手脚,比如判断我们工程下某些java类是不是存在,这些类中某些方法是不是存在,这样及时签名被泄露后恶意者也要把项目中的一些类、方法全部一模一样的保留才能使用我们的.so库文件,又增加一些复杂度。安全没有十全十美,我们能做的就是尽量加大恶意者的破坏难度。