Android NDK JNI 入門筆記-day05-NDK應用簽名校驗

Android NDK JNI 入門筆記目錄 html

開頭

NDK 實踐-應用簽名校驗。java

應用簽名

Android 應用簽名是應用打包過程的重要步驟之一,Google 要求全部的應用必須被簽名才能夠安裝到 Android 操做系統中。android

應用簽名不能保證 APK 不被篡改,只是爲了可以校驗出 APK 是否被篡改。在系統安裝過程當中,若是發現 APK 被篡改,安裝就會失敗。git

NDK 應用簽名校驗

爲了相對安全,一些敏感操做每每會使用 Native 的方式來實現。可是別人能夠經過 APK 文件獲取到咱們的 .so 文件,進而使用咱們的 .sogithub

可是應用簽名的證書只有咱們持有,咱們能夠經過 Native 校驗簽名來判斷是不是咱們本身的應用,若是不是能夠返回錯誤或直接退出應用。算法

動手實踐

像以前同樣建立一個 Native C++ 模板項目數組

項目準備

day05-example-preview

查看證書指紋:

新建的 Android 項目,默認的簽名證書在用戶根目錄的 .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實現Hash算法oracle

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;");

    // 得到當前應用的包名
    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 數組中取出第一個元素
    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

至此,咱們已經學會了在 Android 項目中 Native 進行簽名校驗,應用安全提高了。

代碼:app

NDKJNIday05

參考資料:

Oracle - JNI Types and Data Structures

獲取Android應用簽名的幾種方式

簽名校驗經過NDK實現

相關文章
相關標籤/搜索