由于最近需要加密一些硬编码的字段
apk反编译成功之后,app中的所有硬编码都能看到
常见的加密方式
- 字符串加密(编译时间增加,AS升级插件兼容问题,而且这个反编译完毕之后也能看到加密方法,只能增加破解者根据字符找突破口难度)
- 硬编码写到JNI中(目前采用办法)
硬编码写到JNI中
-
使用NDK开发出来的原生C++代码编译后生成的so库是一个二进制文件,这无疑增加了破解的难度。利用这个特性,可以将客户端敏感信息写在C++代码中,增强应用的安全性。
-
虽然native方法与java代码存在一个对应关系,如包名、路径、方法名称,但这也是可以轻松实现的。所以,将敏感信息存放在so库中有一个不得不考虑的问题就是,万一别人将你的so库直接copy出来拿去用了呢?因此,我们还需要在native层对应用的包名、签名进行鉴权校验,如果不是自己的应用,不返回相关信息,或者直接退出应用!(目前这种方案较多爱奇艺,优酷,等一些软件重新打包之后基本都活验证签名,不一样直接退出或者返回失败)
-
由于不太懂C,所以也是找了份代码改改,我们使用AS来创建一个NDK工程
这里可以看到多了个CPP目录这里就是我们要写的C代码
#include <jni.h>
#include <string>
#include <string.h>
#include <malloc.h>
#include "native-lib.h"
extern "C"
const char *AUTH_KEY = "xxx";
const char *AUTH_KGC_KEY = "assac";
const char *PACKAGE_NAME = "com.xasdasds";
const char *RELEASE_SIGN_MD5 = "2634EFD64B5CDD13A9CCA6049949D26E";
/**
* getApplication
* @param env
* @return j_object
*/
static jobject getApplication(JNIEnv *env) {
jobject application = NULL;
jclass activity_thread_clz = env->FindClass("android/app/ActivityThread");
if (activity_thread_clz != NULL) {
jmethodID currentApplication = env->GetStaticMethodID(
activity_thread_clz, "currentApplication", "()Landroid/app/Application;");
if (currentApplication != NULL) {
application = env->CallStaticObjectMethod(activity_thread_clz, currentApplication);
}
env->DeleteLocalRef(activity_thread_clz);
}
return application;
}
/**
* HexToString
* @param source
* @param dest
* @param sourceLen
*/
static 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;
}
}
}
/**
*
* byteArrayToMd5
* @param env
* @param source
* @return j_string
*/
static 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;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_bdqn_kegongchang_utils_JniUtils_getSignature(JNIEnv *env, jclass type) {
jobject context = getApplication(env);
// 获得Context类
jclass cls = env->GetObjectClass(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 (env)->NewStringUTF(c_pack_name);
}
//如果包名正确返回当前的信息
return (env)->NewStringUTF(AUTH_KEY);
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_bdqn_kegongchang_utils_JniUtils_getCkSignature(JNIEnv *env, jclass type) {
jobject context = getApplication(env);
// 获得Context类
jclass cls = env->GetObjectClass(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 (env)->NewStringUTF(c_pack_name);
}
//如果包名正确返回当前的信息
return (env)->NewStringUTF(AUTH_KGC_KEY);
}
这里是我拷贝网上一段代码,注释很清楚,就是判断当前应用的包名,然后对比,如果一样就返回数据。
public class JniUtils {
//加载so
static {
System.loadLibrary("native-lib");
}
/**
*获取jni层的签名key
* @return
*/
public static native String getSignature();
/**
*获取jni层的签名key
* @return
*/
public static native String getCkSignature();
}
提供了两个方法供调用
- 打包成so
当我们编译的时候已经自动生成了so库直接拷贝就可以使用了,这样做只能增加破解门槛,无法防止破解。