開頭
NDK 實(shí)踐-應(yīng)用簽名校驗(yàn)。
應(yīng)用簽名
Android 應(yīng)用簽名是應(yīng)用打包過程的重要步驟之一,Google 要求所有的應(yīng)用必須被簽名才可以安裝到 Android 操作系統(tǒng)中。
應(yīng)用簽名不能保證 APK 不被篡改,只是為了能夠校驗(yàn)出 APK 是否被篡改。在系統(tǒng)安裝過程中,如果發(fā)現(xiàn) APK 被篡改,安裝就會(huì)失敗。
NDK 應(yīng)用簽名校驗(yàn)
為了相對安全,一些敏感操作往往會(huì)使用 Native 的方式來實(shí)現(xiàn)。但是別人可以通過 APK 文件獲取到我們的 .so 文件,進(jìn)而使用我們的 .so。
但是應(yīng)用簽名的證書只有我們持有,我們可以通過 Native 校驗(yàn)簽名來判斷是否是我們自己的應(yīng)用,如果不是可以返回錯(cuò)誤或直接退出應(yīng)用。
動(dòng)手實(shí)踐
像之前一樣創(chuàng)建一個(gè) Native C++ 模板項(xiàng)目
項(xiàng)目準(zhǔn)備

day05-example-preview
查看證書指紋:
新建的 Android 項(xiàng)目,默認(rèn)的簽名證書在用戶根目錄的 .android 目錄中 ~/.android/debug.keystore

android-default-debug-keystore
$ keytool -list -v -keystore debug.keystore
輸入密鑰庫口令:
android

android-cert-fingerprint
Java 獲取證書指紋
public class SignatureUtil {
public static String getSignatureStr(Context context) {
Signature signature = getSignature(context);
byte[] cert = signature.toByteArray();
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
MessageDigest sha256 = MessageDigest.getInstance("SHA256");
byte[] md5Key = md5.digest(cert);
byte[] sha1Key = sha1.digest(cert);
byte[] sha256Key = sha256.digest(cert);
return String.format("MD5: %s\n\nSHA1: %s\n\nSHA-256: %s",
byteArrayToString(md5Key),
byteArrayToString(sha1Key),
byteArrayToString(sha256Key)
);
} catch (Exception e) {
return "";
}
}
public static Signature getSignature(Context argContext) {
Signature signature = null;
try {
String packageName = argContext.getPackageName();
PackageManager packageManager = argContext.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, GET_SIGNATURES);
Signature[] signatures = packageInfo.signatures;
signature = signatures[0];
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return signature;
}
private static String byteArrayToString(byte[] array) {
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < array.length; i++) {
String appendString = Integer.toHexString(0xFF & array[i]).toUpperCase();
if (appendString.length() == 1)
hexString.append("0");
hexString.append(appendString);
if(i < array.length - 1)
hexString.append(":");
}
return hexString.toString();
}
}
Native 獲取證書指紋
這里用到了 HASH 算法,Android NDK JNI 入門筆記-day04-NDK實(shí)現(xiàn)Hash算法
jbyteArray getSignatureByte(JNIEnv *env, jobject context);
void hashByteArray(HASH type, const void* data, size_t numBytes, char* resultData);
void formatSignature(char* data, char* resultData);
extern "C"
JNIEXPORT jstring JNICALL
Java_com_ihubin_ndkjni_NativeUtil_getSignature(JNIEnv *env, jclass clazz, jobject context) {
jbyteArray cert_byteArray = getSignatureByte(env, context);
jsize size = env->GetArrayLength(cert_byteArray);
jbyte* jbyteArray = new jbyte[size];
env->GetByteArrayRegion(cert_byteArray, 0, size, jbyteArray);
char certMD5[128] = {0};
hashByteArray(HASH_MD5, jbyteArray, size, certMD5);
char certSHA1[128] = {0};
hashByteArray(HASH_SHA1, jbyteArray, size, certSHA1);
char certSHA256[128] = {0};
hashByteArray(HASH_SHA256, jbyteArray, size, certSHA256);
LOGD("MD5: %s", certMD5);
LOGD("SHA1: %s", certSHA1);
LOGD("SHA256: %s", certSHA256);
char resultStr[1000] = {0};
strcat(resultStr, "MD5: ");
strcat(resultStr, certMD5);
strcat(resultStr, "\n\nSHA1: ");
strcat(resultStr, certSHA1);
strcat(resultStr, "\n\nSHA256: ");
strcat(resultStr, certSHA256);
return env->NewStringUTF(resultStr);
}
// Native 從 Context 中獲取簽名
jbyteArray getSignatureByte(JNIEnv *env, jobject context) {
// Context 的類
jclass context_clazz = env->GetObjectClass(context);
// 得到 getPackageManager 方法的 ID
jmethodID methodID_getPackageManager = env->GetMethodID(context_clazz, "getPackageManager", "()Landroid/content/pm/PackageManager;");
// 獲得 PackageManager 對象
jobject packageManager = env->CallObjectMethod(context, methodID_getPackageManager);
// 獲得 PackageManager 類
jclass packageManager_clazz=env->GetObjectClass(packageManager);
// 得到 getPackageInfo 方法的 ID
jmethodID methodID_getPackageInfo=env->GetMethodID(packageManager_clazz,"getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
// 得到 getPackageName 方法的 ID
jmethodID methodID_getPackageName = env->GetMethodID(context_clazz,"getPackageName", "()Ljava/lang/String;");
// 獲得當(dāng)前應(yīng)用的包名
jobject application_package_obj = env->CallObjectMethod(context, methodID_getPackageName);
jstring application_package = static_cast<jstring>(application_package_obj);
const char* package_name = env->GetStringUTFChars(application_package, 0);
LOGD("packageName: %s", package_name);
// 獲得 PackageInfo
jobject packageInfo = env->CallObjectMethod(packageManager, methodID_getPackageInfo, application_package, 64);
jclass packageinfo_clazz = env->GetObjectClass(packageInfo);
// 獲取簽名
jfieldID fieldID_signatures = env->GetFieldID(packageinfo_clazz, "signatures", "[Landroid/content/pm/Signature;");
jobjectArray signature_arr = (jobjectArray)env->GetObjectField(packageInfo, fieldID_signatures);
// Signature 數(shù)組中取出第一個(gè)元素
jobject signature = env->GetObjectArrayElement(signature_arr, 0);
// 讀 signature 的 ByteArray
jclass signature_clazz = env->GetObjectClass(signature);
jmethodID methodID_byteArray = env->GetMethodID(signature_clazz, "toByteArray", "()[B");
jobject cert_obj = env->CallObjectMethod(signature, methodID_byteArray);
jbyteArray cert_byteArray = static_cast<jbyteArray>(cert_obj);
return cert_byteArray;
}
// 獲得簽名的 MD5 SHA1 SHA256
void hashByteArray(HASH type, const void* data, size_t numBytes, char* resultData){
if(type == HASH_MD5) {
MD5 md5;
std::string md5String = md5(data, numBytes);
int len = md5String.length()+1;
char * tabStr = new char [md5String.length()+1];
strcpy(tabStr, md5String.c_str());
formatSignature(tabStr, resultData);
} else if(type == HASH_SHA1) {
SHA1 sha1;
std::string sha1String = sha1(data, numBytes);
char * tabStr = new char [sha1String.length()+1];
strcpy(tabStr, sha1String.c_str());
formatSignature(tabStr, resultData);
} else if(type == HASH_SHA256) {
SHA256 sha256;
std::string sha256String = sha256(data, numBytes);
char * tabStr = new char [sha256String.length()+1];
strcpy(tabStr, sha256String.c_str());
formatSignature(tabStr, resultData);
}
}
// 格式化輸出
void formatSignature(char* data, char* resultData) {
int resultIndex = 0;
int length = strlen(data);
for(int i = 0; i < length; i++) {
resultData[resultIndex] = static_cast<char>(toupper(data[i]));
if(i % 2 == 1 && i != length -1) {
resultData[resultIndex+1] = ':';
resultIndex+=2;
} else {
resultIndex++;
}
}
}
最終效果

day05-example-result
至此,我們已經(jīng)學(xué)會(huì)了在 Android 項(xiàng)目中 Native 進(jìn)行簽名校驗(yàn),應(yīng)用安全提升了。
代碼:
參考資料:
Oracle - JNI Types and Data Structures