咱们常常会有些敏感的信息须要客户端加解密,但android很容易被反编译,因此咱们写在客户端里的密钥终究得不到安全,可能有人会想把加密方式写在C代码中,生成.so供APK使用,但是别人不关心你C里的代码,直接把你的so文件给拿去用就能够了,那么有没有一种安全的措施来加大难度呢,我说一下咱们项目中若是解决客户端加密安全方案.java
首先加密的代码仍然写在C代码中,至少C被反编译出来是汇编代码,加大阅读难度,另一点就是如何让咱们的.so文件只容许在咱们本身的APK中使用,而不被别人拿去使用呢,这就须要签名验证这作一些文章,当程序调用.so时咱们能够C代码中检查当前程序的签名是否是咱们的,条件成立才进行加解密操做,固然签名泄露这个就另说了,毕竟没有百分百的安全,先来看看C代码 #include <string.h>
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>android
//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 {app
static{ System.loadLibrary("mylib"); } public native String getScriptData(Context context);
}this
上面的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对象code
第一步经过后,第二步获取当前程序的包名,进行对比,包名相同后接下来咱们就要获取so的调用者程序的签名了,这一步就是安全的关键性所在,若是以上条件都经过了,逻辑就到了咱们进行加解密的地方。对象
其实我在签名判断完后,又在安全性上作了一些小的手脚,好比判断咱们工程下某些java类是否是存在,这些类中某些方法是否是存在,这样及时签名被泄露后恶意者也要把项目中的一些类、方法所有如出一辙的保留才能使用咱们的.so库文件,又增长一些复杂度。安全没有十全十美,咱们能作的就是尽可能加大恶意者的破坏难度。ip