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);
}
}
5.总结
以上就是此次事件native的相关代码,至于如何生成so库可以自行百度。从此次事件中需要反思的几点是:
- 安全性的认识,安全无小事
- 发布出去的包必须走加固流程,为了防止疏漏,禁止人工打包加固,全部通过脚本实现
- 服务端增加相关风险的报警机制
粉丝技术交流裙
最后笔者收集整理了一份Flutter高级入门进阶资料PDF
以下是资料目录和内容部分截图
里面包括详细的知识点讲解分析,带你一个星期入门Flutter。还有130个进阶学习项目实战视频教程,让你秒变大前端。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
8499)]
里面包括详细的知识点讲解分析,带你一个星期入门Flutter。还有130个进阶学习项目实战视频教程,让你秒变大前端。
[外链图片转存中…(img-HwfD0BLx-1714430998500)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!