本篇内容主要讲解“如何理解Android敏感数据泄露”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何理解Android敏感数据泄露”吧!
公司主营业务:网站设计制作、成都网站建设、移动网站开发等业务。帮助企业客户真正实现互联网宣传,提高企业的竞争能力。成都创新互联公司是一支青春激扬、勤奋敬业、活力青春激扬、勤奋敬业、活力澎湃、和谐高效的团队。公司秉承以“开放、自由、严谨、自律”为核心的企业文化,感谢他们对我们的高要求,感谢他们从不同领域给我们带来的挑战,让我们激情的团队有机会用头脑与智慧不断的给客户带来惊喜。成都创新互联公司推出含山免费做网站回馈大家。
1.事件始末
一个平淡的午后,我还悠哉悠哉的敲着代码品着茶。突然服务端同事告诉我,关注接口正在被机械式调用,怀疑是有人在使用脚本刷接口(目的主要是从平台导流)。
纳尼?不会吧,因为据我所知接口请求是做了加密处理的,除非知道加密的密钥和加密方式,不然是不会调用成功的,一定是你感觉错了。然而当服务端同事把接口调用日志发给我看时,彻底否定了我的侥幸心理。
接口调用频率固定为1s 一次
被关注者的id每次调用依次加一(目前业务上用户id的生成是按照注册时间依次递增的)
加密的密钥始终使用固定的一个(正常的是在固定的几个密钥中每次会随机使用一个)
综合以上三点就可以断定,肯定是存在刷接口的行为了。
2.事件分析
既然上述刷接口的行为成立,也就意味着密钥和加密方式被对方知道了,原因无非是以下两点:
内部人员泄露
apk被破解
经过确认基本排除了第一点,那就只剩下apk被破解了,可是apk发布出去的包是进行过加固和混淆处理的,难道对方脱壳了?不管三七二十一,自己先来反编译试试。于是乎从最近发布的版本一个一个去反编译,最后在反编译到较早前的一个版本时发现,保存密钥和加密的工具类居然源码完全暴露了。
炸了锅了,排查了一下这个版本居然未加固过就发布出去了,而且这个加密工具类未被混淆。虽然还不太清楚对方是不是按照这种方式获取的密钥和加密算法,但无疑这是客户端存在的一个安全漏洞。
3.事件处理
既然已经发现了上述问题,那就要想办法解决。首先不考虑加固,如何尽最大可能保证客户端中的敏感数据不泄露?另一方面即使对方想要破解,也要想办法设障,增大破解难度。想到这里基本就大致确定了一个思路:使用NDK,将敏感数据和加密方式放到native层,因为C++代码编译后生成的so库是一个二进制文件,这无疑会增加破解的难度。利用这个特性,可以将客户端的敏感数据写在C++代码中,从而增强应用的安全性。 说干就干吧!!!
1.首先创建了加密工具类:
public class HttpKeyUtil { static { System.loadLibrary("jniSecret"); } //根据随机值去获取密钥 public static native String getHttpSecretKey(int index); //将待加密的数据传入,返回加密后的结果 public static native String getSecretValue(byte[] bytes); }
2.生成相应的头文件:
#include#ifndef _Included_com_test_util_HttpKeyUtil #define _Included_com_test_util_HttpKeyUtil #ifdef __cplusplus extern "C" { #endif 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); #ifdef __cplusplus } #endif #endif
3.编写相应的cpp文件:
在相应的Module中创建jni目录,将com_test_util_HttpKeyUtil.h拷贝进来,然后再创建com_test_util_HttpKeyUtil.cpp文件
#include#include #include #include "com_test_util_HttpKeyUtil.h" 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 (随机数条件1) { return env->NewStringUTF(KEY1); } else if (随机数条件2) { return env->NewStringUTF(KEY2); } else if (随机数条件3) { return env->NewStringUTF(KEY3); } else { return env->NewStringUTF(UNKNOWN); } } extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getSecretValue (JNIEnv *env, jclass cls, jbyteArray jbyteArray1) { //加密算法各有不同,这里我就用md5做个示范 return toMd5(env, jbyteArray1); } //md5 jstring toMd5(JNIEnv *env, jbyteArray source) { // MessageDigest jclass classMessageDigest = env->FindClass("java/security/MessageDigest"); // MessageDigest.getInstance() jmethodID midGetInstance = env->GetStaticMethodID(classMessageDigest, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;"); // MessageDigest object jobject objMessageDigest = env->CallStaticObjectMethod(classMessageDigest, midGetInstance, env->NewStringUTF("md5")); jmethodID midUpdate = env->GetMethodID(classMessageDigest, "update", "([B)V"); env->CallVoidMethod(objMessageDigest, midUpdate, source); // Digest jmethodID 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); // 在末尾补\0 *(char_result + intArrayLength * 2) = '\0'; jstring stringResult = env->NewStringUTF(char_result); // release env->ReleaseByteArrayElements(objArraySign, byte_array_elements, JNI_ABORT); // 指针 free(char_result); return stringResult; } //转换为16进制字符串 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.事件就此结束?
到这里就此结束了?too yuang too simple!!!虽然将密钥和加密算法写在了c++中,貌似好像是比较安全了。但是但是万一别人反编译后,拿到c++代码最终生成的so库,然后直接调用so库里的方法去获取密钥并调用加密方法怎么破?看来我们还是要加一步身份校验才行:即在native层对应用的包名、签名进行鉴权校验,校验通过才返回正确结果。下面就是获取apk包名和签名校验的代码:
const char *PACKAGE_NAME = "你的ApplicationId"; //(签名的md5值自己可以写方法获取,或者用签名工具直接获取,一般对接微信sdk的时候也会要应用签名的MD5值) const char *SIGN_MD5 = "你的应用签名的MD5值注意是大写"; //获取Application实例 jobject getApplication(JNIEnv *env) { jobject application = NULL; //这里是你的Application的类路径,混淆时注意不要混淆该类和该类获取实例的方法比如getInstance jclass 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; //获取应用签名的MD5值并判断是否与本应用的一致 jboolean getSignature(JNIEnv *env) { LOGD("getSignature isRight: %d", isRight ? 1 : 0); if (!isRight) {//避免每次都进行校验浪费资源,只要第一次校验通过后,后边就不在进行校验 jobject context = getApplication(env); // 获得Context类 jclass cls = env->FindClass("android/content/Context"); // 得到getPackageManager方法的ID jmethodID mid = env->GetMethodID(cls, "getPackageManager", "()Landroid/content/pm/PackageManager;"); // 获得应用包的管理器 jobject pm = env->CallObjectMethod(context, mid); // 得到getPackageName方法的ID mid = env->GetMethodID(cls, "getPackageName", "()Ljava/lang/String;"); // 获得当前应用包名 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方法的ID mid = env->GetMethodID(cls, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"); // 获得应用包的信息 jobject packageInfo = env->CallObjectMethod(pm, mid, packageName, 0x40); //GET_SIGNATURES = 64; // 获得PackageInfo 类 cls = env->GetObjectClass(packageInfo); // 获得签名数组属性的ID jfieldID fid = env->GetFieldID(cls, "signatures", "[Landroid/content/pm/Signature;"); // 得到签名数组 jobjectArray signatures = (jobjectArray) env->GetObjectField(packageInfo, fid); // 得到签名 jobject signature = env->GetObjectArrayElement(signatures, 0); // 获得Signature类 cls = env->GetObjectClass(signature); mid = env->GetMethodID(cls, "toByteArray", "()[B"); // 当前应用签名信息 jbyteArray signatureByteArray = (jbyteArray) env->CallObjectMethod(signature, mid); //转成jstring jstring 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; } //有了校验的方法,所以我们要对第3步中,获取密钥和加密方法的进行修改,添加校验的逻辑 extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getHttpSecretKey (JNIEnv *env, jclass cls, jint index) { if (getSignature(env)){//校验通过 if (随机数条件1) { return env->NewStringUTF(KEY1); } else if (随机数条件2) { return env->NewStringUTF(KEY2); } else if (随机数条件3) { return env->NewStringUTF(KEY3); } else { return env->NewStringUTF(UNKNOWN); } }else { return env->NewStringUTF(UNKNOWN); } } extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getSecretValue (JNIEnv *env, jclass cls, jbyteArray jbyteArray1) { //加密算法各有不同,这里我就用md5做个示范 if (getSignature(env)){//校验通过 return toMd5(env, jbyteArray1); }else { return env->NewStringUTF(UNKNOWN); } } 作者:AirSj 链接:https://juejin.im/post/6862732328406351879 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
到此,相信大家对“如何理解Android敏感数据泄露”有了更深的了解,不妨来实际操作一番吧!这里是创新互联网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!