Android敏感數(shù)據(jù)泄露引發(fā)的思考
塵埃落定!清華才子王垠加入華為職級(jí)22,前阿里P10趙海平加入字節(jié)跳動(dòng),職級(jí)或?yàn)?+ 百度網(wǎng)盤“破解版”,Pandownload開發(fā)者被抓
事件始末
接口調(diào)用頻率固定為
1s?一次被關(guān)注者的
id每次調(diào)用依次加一(目前業(yè)務(wù)上用戶id的生成是按照注冊時(shí)間依次遞增的)加密的
密鑰始終使用固定的一個(gè)(正常的是在固定的幾個(gè)密鑰中每次會(huì)隨機(jī)使用一個(gè))??
事件分析
內(nèi)部人員泄露 ?
apk被破解??

既然已經(jīng)發(fā)現(xiàn)了上述問題,那就要想辦法解決。首先不考慮加固,如何盡最大可能保證客戶端中的敏感數(shù)據(jù)不泄露?另一方面即使對(duì)方想要破解,也要想辦法設(shè)障,增大破解難度。想到這里基本就大致確定了一個(gè)思路:使用NDK,將敏感數(shù)據(jù)和加密方式放到native層,因?yàn)镃++代碼編譯后生成的so庫是一個(gè)二進(jìn)制文件,這無疑會(huì)增加破解的難度。利用這個(gè)特性,可以將客戶端的敏感數(shù)據(jù)寫在C++代碼中,從而增強(qiáng)應(yīng)用的安全性。??說干就干吧!!!
public class HttpKeyUtil {static {System.loadLibrary("jniSecret");}//根據(jù)隨機(jī)值去獲取密鑰public static native String getHttpSecretKey(int index);//將待加密的數(shù)據(jù)傳入,返回加密后的結(jié)果public static native String getSecretValue(byte[] bytes);}
com_test_util_HttpKeyUtil.h
extern "C" {JNIEXPORT jstring JNICALL Java_com_esky_common_component_util_HttpKeyUtil_getHttpSecretKey(JNIEnv *, jclass, jint);JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getSecretValue(JNIEnv *, jclass, jbyteArray);}

extern "C"const char *KEY1 = "密鑰1";const char *KEY2 = "密鑰2";const char *KEY3 = "密鑰3";const char *UNKNOWN = "unknown";jstring toMd5(JNIEnv *pEnv, jbyteArray pArray);extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getHttpSecretKey(JNIEnv *env, jclass cls, jint index) {if (隨機(jī)數(shù)條件1) {return env->NewStringUTF(KEY1);} else if (隨機(jī)數(shù)條件2) {return env->NewStringUTF(KEY2);} else if (隨機(jī)數(shù)條件3) {return env->NewStringUTF(KEY3);} else {return env->NewStringUTF(UNKNOWN);}}extern "C" JNIEXPORT jstring JNICALLJava_com_test_util_HttpKeyUtil_getSecretValue(JNIEnv *env, jclass cls, jbyteArray jbyteArray1) {//加密算法各有不同,這里我就用md5做個(gè)示范return toMd5(env, jbyteArray1);}//md5jstring toMd5(JNIEnv *env, jbyteArray source) {// MessageDigestjclass classMessageDigest = env->FindClass("java/security/MessageDigest");// MessageDigest.getInstance()jmethodID midGetInstance = env->GetStaticMethodID(classMessageDigest, "getInstance","(Ljava/lang/String;)Ljava/security/MessageDigest;");// MessageDigest objectjobject objMessageDigest = env->CallStaticObjectMethod(classMessageDigest, midGetInstance,env->NewStringUTF("md5"));jmethodID midUpdate = env->GetMethodID(classMessageDigest, "update", "([B)V");env->CallVoidMethod(objMessageDigest, midUpdate, source);// DigestjmethodID midDigest = env->GetMethodID(classMessageDigest, "digest", "()[B");jbyteArray objArraySign = (jbyteArray) env->CallObjectMethod(objMessageDigest, midDigest);jsize intArrayLength = env->GetArrayLength(objArraySign);jbyte *byte_array_elements = env->GetByteArrayElements(objArraySign, NULL);size_t length = (size_t) intArrayLength * 2 + 1;char *char_result = (char *) malloc(length);memset(char_result, 0, length);toHexStr((const char *) byte_array_elements, char_result, intArrayLength);// 在末尾補(bǔ)\0*(char_result + intArrayLength * 2) = '\0';jstring stringResult = env->NewStringUTF(char_result);// releaseenv->ReleaseByteArrayElements(objArraySign, byte_array_elements, JNI_ABORT);// 指針free(char_result);return stringResult;}//轉(zhuǎn)換為16進(jìn)制字符串void toHexStr(const char *source, char *dest, int sourceLen) {short i;char highByte, lowByte;for (i = 0; i < sourceLen; i++) {highByte = source[i] >> 4;lowByte = (char) (source[i] & 0x0f);highByte += 0x30;if (highByte > 0x39) {dest[i * 2] = (char) (highByte + 0x07);} else {dest[i * 2] = highByte;}lowByte += 0x30;if (lowByte > 0x39) {dest[i * 2 + 1] = (char) (lowByte + 0x07);} else {dest[i * 2 + 1] = lowByte;}}}
4.事件就此結(jié)束?
const char *PACKAGE_NAME = "你的ApplicationId";//(簽名的md5值自己可以寫方法獲取,或者用簽名工具直接獲取,一般對(duì)接微信sdk的時(shí)候也會(huì)要應(yīng)用簽名的MD5值)const char *SIGN_MD5 = "你的應(yīng)用簽名的MD5值注意是大寫";//獲取Application實(shí)例jobject getApplication(JNIEnv *env) {jobject application = NULL;//這里是你的Application的類路徑,混淆時(shí)注意不要混淆該類和該類獲取實(shí)例的方法比如getInstancejclass baseapplication_clz = env->FindClass("com/test/component/BaseApplication");if (baseapplication_clz != NULL) {jmethodID currentApplication = env->GetStaticMethodID(baseapplication_clz, "getInstance","()Lcom/test/component/BaseApplication;");if (currentApplication != NULL) {application = env->CallStaticObjectMethod(baseapplication_clz, currentApplication);}env->DeleteLocalRef(baseapplication_clz);}return application;}bool isRight = false;//獲取應(yīng)用簽名的MD5值并判斷是否與本應(yīng)用的一致jboolean getSignature(JNIEnv *env) {LOGD("getSignature isRight: %d", isRight ? 1 : 0);if (!isRight) {//避免每次都進(jìn)行校驗(yàn)浪費(fèi)資源,只要第一次校驗(yàn)通過后,后邊就不在進(jìn)行校驗(yàn)jobject context = getApplication(env);// 獲得Context類jclass cls = env->FindClass("android/content/Context");// 得到getPackageManager方法的IDjmethodID mid = env->GetMethodID(cls, "getPackageManager","()Landroid/content/pm/PackageManager;");// 獲得應(yīng)用包的管理器jobject pm = env->CallObjectMethod(context, mid);// 得到getPackageName方法的IDmid = env->GetMethodID(cls, "getPackageName", "()Ljava/lang/String;");// 獲得當(dāng)前應(yīng)用包名jstring packageName = (jstring) env->CallObjectMethod(context, mid);const char *c_pack_name = env->GetStringUTFChars(packageName, NULL);// 比較包名,若不一致,直接return包名if (strcmp(c_pack_name, PACKAGE_NAME) != 0) {return false;}// 獲得PackageManager類cls = env->GetObjectClass(pm);// 得到getPackageInfo方法的IDmid = env->GetMethodID(cls, "getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");// 獲得應(yīng)用包的信息jobject packageInfo = env->CallObjectMethod(pm, mid, packageName,0x40); //GET_SIGNATURES = 64;// 獲得PackageInfo 類cls = env->GetObjectClass(packageInfo);// 獲得簽名數(shù)組屬性的IDjfieldID fid = env->GetFieldID(cls, "signatures", "[Landroid/content/pm/Signature;");// 得到簽名數(shù)組jobjectArray signatures = (jobjectArray) env->GetObjectField(packageInfo, fid);// 得到簽名jobject signature = env->GetObjectArrayElement(signatures, 0);// 獲得Signature類cls = env->GetObjectClass(signature);mid = env->GetMethodID(cls, "toByteArray", "()[B");// 當(dāng)前應(yīng)用簽名信息jbyteArray signatureByteArray = (jbyteArray) env->CallObjectMethod(signature, mid);//轉(zhuǎn)成jstringjstring str = toMd5(env, signatureByteArray);char *c_msg = (char *) env->GetStringUTFChars(str, 0);LOGD("getSignature release sign md5: %s", c_msg);isRight = strcmp(c_msg, SIGN_MD5) == 0;return isRight;}return isRight;}//有了校驗(yàn)的方法,所以我們要對(duì)第3步中,獲取密鑰和加密方法的進(jìn)行修改,添加校驗(yàn)的邏輯extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getHttpSecretKey(JNIEnv *env, jclass cls, jint index) {if (getSignature(env)){//校驗(yàn)通過if (隨機(jī)數(shù)條件1) {return env->NewStringUTF(KEY1);} else if (隨機(jī)數(shù)條件2) {return env->NewStringUTF(KEY2);} else if (隨機(jī)數(shù)條件3) {return env->NewStringUTF(KEY3);} else {return env->NewStringUTF(UNKNOWN);}}else {return env->NewStringUTF(UNKNOWN);}}extern "C" JNIEXPORT jstring JNICALLJava_com_test_util_HttpKeyUtil_getSecretValue(JNIEnv *env, jclass cls, jbyteArray jbyteArray1) {//加密算法各有不同,這里我就用md5做個(gè)示范if (getSignature(env)){//校驗(yàn)通過return toMd5(env, jbyteArray1);}else {return env->NewStringUTF(UNKNOWN);}}
5.總結(jié)
安全性的認(rèn)識(shí),安全無小事
發(fā)布出去的包必須走加固流程,為了防止疏漏,禁止人工打包加固,全部通過腳本實(shí)現(xiàn)
服務(wù)端增加相關(guān)風(fēng)險(xiǎn)的報(bào)警機(jī)制

如有收獲,歡迎「分享?
」
「點(diǎn)贊
」「評(píng)論?
」
看完本文有收獲?請(qǐng)轉(zhuǎn)發(fā)分享給更多人
? 開發(fā)者全社區(qū)?
評(píng)論
圖片
表情
