咱們常常會有些敏感的信息須要客戶端加解密,但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