國(guó)內(nèi)android市場(chǎng)的環(huán)境比較混亂碗殷,大小市場(chǎng)有數(shù)百家,應(yīng)用被修改或加廣告病毒后二次打包發(fā)布也是常有的事情仿粹,對(duì)開(kāi)發(fā)者和用戶都造成了損失
經(jīng)各方資料研究搁吓,在C++中做簽名檢測(cè)處理,安全性更高一些吭历,結(jié)合網(wǎng)上資料堕仔,寫(xiě)下此文
簽名檢測(cè)邏輯
- 獲取應(yīng)用簽名生成的MD5值寫(xiě)入C++中 (C++中會(huì)打印出當(dāng)前的MD5,后面會(huì)說(shuō))
- 獲取Application
- 獲取應(yīng)用的簽名信息
- 將簽名信息MD5化
- 匹配簽名MD5是否與寫(xiě)入的相同
- 簽名不同時(shí)在JNI_OnLoad 中返回 -1
- 在App的代碼中加載so文件,會(huì)自動(dòng)調(diào)用JNI_OnLoad,若返回-1晌区,App Crash掉
NDK編譯
NDK編譯使用的實(shí)驗(yàn)性NDK構(gòu)建插件 Experimental Plugin
只要是因?yàn)樵摬寮С諧語(yǔ)言Debug調(diào)試,但是使用該插件需要修改Module的Build.gradle文件摩骨,不太建議使用在開(kāi)發(fā)的項(xiàng)目中,可以使用之前的NDK或CMake來(lái)編譯NDK
這里不介紹實(shí)驗(yàn)性NDK插件了朗若,附上使用手冊(cè)Experimental Plugin use guide
Java代碼上的邏輯
/**
* 獲取Application恼五,ActivityThread是一個(gè)不被公開(kāi)的類,java代碼是不可直接調(diào)用的
* 對(duì)應(yīng)C++代碼中的static jobject getApplication(JNIEnv *env)
*/
Application application = ActivityThread.currentApplication();
/**
* 獲取Signature數(shù)據(jù)
* 對(duì)應(yīng)C++ 的jstring loadSignature(JNIEnv *env, jobject * *context) 方法
*/
PackageManager manager = application.getPackageManager();
String packageName = application.getPackageName();
PackageInfo packageInfo = manager.getPackageInfo(packageName,PackageManager.GET_SIGNATURES);
Signature signature = packageInfo.signatures[0];
byte[] chars = signature.toByteArray();
/**
* 將數(shù)據(jù)MD5化
* 對(duì)應(yīng)C++的jstring ToMd5(JNIEnv *env, jbyteArray source)
*/
MessageDigest digest = MessageDigest.getInstance("md5");
digest.update(chars);
byte[] objArraySign = digest.digest(chars);
//MD5化的C++ 代碼
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);
// 將byte數(shù)組轉(zhuǎn)換成16進(jìn)制字符串哭懈,發(fā)現(xiàn)這里不用強(qiáng)轉(zhuǎn)灾馒,jbyte和unsigned char應(yīng)該字節(jié)數(shù)是一樣的
ByteToHexStr((const char *) byte_array_elements, char_result, intArrayLength);
// 在末尾補(bǔ)\0
*(char_result + intArrayLength * 2) = '\0';
jstring stringResult = env->NewStringUTF(char_result);
//檢測(cè)當(dāng)前應(yīng)用的簽名MD5與我們?cè)O(shè)置的MD5是否一致
奉上C++ 代碼
#include <jni.h>
#include <string.h>
#include <android/log.h>
#include <malloc.h>
#define LOG_TAG "native-dev"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
const char *APP_SIGNATURE = "10645EA8A12BE7A2C04B1F81DF3B4D90";
void ByteToHexStr(const char *source, char *dest, int sourceLen) {
short i;
char highByte, lowByte;
for (i = 0; i < sourceLen; i++) {
highByte = source[i] >> 4;
lowByte = source[i] & 0x0f;
highByte += 0x30;
if (highByte > 0x39) {
dest[i * 2] = highByte + 0x07;
} else {
dest[i * 2] = highByte;
}
lowByte += 0x30;
if (lowByte > 0x39) {
dest[i * 2 + 1] = lowByte + 0x07;
} else {
dest[i * 2 + 1] = lowByte;
}
}
}
// byte數(shù)組轉(zhuǎn)MD5字符串
jstring ToMd5(JNIEnv *env, jbyteArray source) {
// MessageDigest類
jclass classMessageDigest = env->FindClass("java/security/MessageDigest");
// MessageDigest.getInstance()靜態(tài)方法
jmethodID midGetInstance = env->GetStaticMethodID(classMessageDigest, "getInstance",
"(Ljava/lang/String;)Ljava/security/MessageDigest;");
// MessageDigest object
jobject objMessageDigest = env->CallStaticObjectMethod(classMessageDigest, midGetInstance,
env->NewStringUTF("md5"));
// update方法,這個(gè)函數(shù)的返回值是void遣总,寫(xiě)V
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);
// 將byte數(shù)組轉(zhuǎn)換成16進(jìn)制字符串睬罗,發(fā)現(xiàn)這里不用強(qiáng)轉(zhuǎn),jbyte和unsigned char應(yīng)該字節(jié)數(shù)是一樣的
ByteToHexStr((const char *) byte_array_elements, char_result, intArrayLength);
// 在末尾補(bǔ)\0
*(char_result + intArrayLength * 2) = '\0';
jstring stringResult = env->NewStringUTF(char_result);
// release
env->ReleaseByteArrayElements(objArraySign, byte_array_elements, JNI_ABORT);
// 釋放指針使用free
free(char_result);
env->DeleteLocalRef(classMessageDigest);
env->DeleteLocalRef(objMessageDigest);
return stringResult;
}
//獲取應(yīng)用簽名
jstring loadSignature(JNIEnv *env, jobject context) {
// 獲得Context類
jclass cls = env->GetObjectClass(context);
// 得到getPackageManager方法的ID
jmethodID mid = env->GetMethodID(cls, "getPackageManager",
"()Landroid/content/pm/PackageManager;");
// 獲得應(yīng)用包的管理器
jobject pm = env->CallObjectMethod(context, mid);
// 得到getPackageName方法的ID
mid = env->GetMethodID(cls, "getPackageName", "()Ljava/lang/String;");
// 獲得當(dāng)前應(yīng)用包名
jstring packageName = (jstring) env->CallObjectMethod(context, mid);
// 獲得PackageManager類
cls = env->GetObjectClass(pm);
// 得到getPackageInfo方法的ID
mid = 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ù)組屬性的ID
jfieldID 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);
// 得到toCharsString方法的ID
mid = env->GetMethodID(cls, "toByteArray", "()[B");
// 返回當(dāng)前應(yīng)用簽名信息
jbyteArray signatureByteArray = (jbyteArray) env->CallObjectMethod(signature, mid);
return ToMd5(env, signatureByteArray);
}
//檢測(cè)簽名是否匹配
jboolean checkSignature(
JNIEnv *env, jobject context) {
jstring appSignature = loadSignature(env,
context); // 當(dāng)前 App 的簽名
jstring releaseSignature = env->NewStringUTF(APP_SIGNATURE); // 發(fā)布時(shí)候的簽名
const char *charAppSignature = env->GetStringUTFChars(appSignature, NULL);
const char *charReleaseSignature = env->GetStringUTFChars(releaseSignature, NULL);
// LOGI(" start cmp getSignature");
__android_log_print(ANDROID_LOG_INFO, LOG_TAG, charAppSignature);
// LOGI(" start cmp getReleaseSignature");
// __android_log_print(ANDROID_LOG_INFO, LOG_TAG, charAppSignature);
jboolean result = JNI_FALSE;
// 比較是否相等
if (charAppSignature != NULL && charReleaseSignature != NULL) {
if (strcmp(charAppSignature, charReleaseSignature) == 0) {
result = JNI_TRUE;
}
}
env->ReleaseStringUTFChars(appSignature, charAppSignature);
env->ReleaseStringUTFChars(releaseSignature, charReleaseSignature);
return result;
}
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);
} else {
// LOGE("Cannot find method: currentApplication() in ActivityThread.");
}
env->DeleteLocalRef(activity_thread_clz);
} else {
// LOGE("Cannot find class: android.app.ActivityThread");
}
return application;
}
/**
* 檢查加載該so的應(yīng)用的簽名旭斥,與預(yù)置的簽名是否一致
*/
static jboolean checkSignature(JNIEnv *env) {
// 調(diào)用 getContext 方法得到 Context 對(duì)象
jobject appContext = getApplication(env);
if (appContext != NULL) {
jboolean signatureValid = checkSignature(
env, appContext);
return signatureValid;
}
return JNI_FALSE;
}
/**
* 加載 so 文件的時(shí)候容达,會(huì)觸發(fā) OnLoad
* 檢測(cè)失敗,返回 -1琉预,App 就會(huì) Crash
*/
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
// LOGI(" JNI_OnLoad ");
if (vm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
// LOGI(" start checkSignature ");
if (checkSignature(env) != JNI_TRUE) {
// LOGI(" checkSignature = false ");
// 檢測(cè)不通過(guò)董饰,返回 -1 就會(huì)使 App crash
return -1;
}
return JNI_VERSION_1_6;
}
在C++的 checkSignature方法中,
__android_log_print(ANDROID_LOG_INFO, LOG_TAG, charAppSignature);
此代碼會(huì)打印出當(dāng)前應(yīng)用的簽名MD5碼圆米,也是我們需要寫(xiě)在C++中用于檢測(cè)的MD5碼卒暂,獲取到之后寫(xiě)C++中并注釋掉這行代碼
加載jni
static{
System.loadLibrary("SignatureLib");
}
完整代碼的GitHub地址
https://github.com/junzLiu/JniTest
參考資料
- 前人方案代碼 https://github.com/ksxkq/CheckSignatureInNativeSample
- Experimental Plugin User Guide http://tools.android.com/tech-docs/new-build-system/gradle-experimental