• Android NDK JNI 入门笔记-day05-NDK应用签名校验


    * Android NDK JNI 入门笔记目录 *

    开头

    NDK 实践-应用签名校验。

    应用签名

    Android 应用签名是应用打包过程的重要步骤之一,Google 要求所有的应用必须被签名才可以安装到 Android 操作系统中。

    应用签名不能保证 APK 不被篡改,只是为了能够校验出 APK 是否被篡改。在系统安装过程中,如果发现 APK 被篡改,安装就会失败。

    NDK 应用签名校验

    为了相对安全,一些敏感操作往往会使用 Native 的方式来实现。但是别人可以通过 APK 文件获取到我们的 .so 文件,进而使用我们的 .so

    但是应用签名的证书只有我们持有,我们可以通过 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
    
    SHA1: %s
    
    SHA-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算法

    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, "
    
    SHA1: ");
        strcat(resultStr, certSHA1);
        strcat(resultStr, "
    
    SHA256: ");
        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 进行签名校验,应用安全提升了。


    代码:

    NDKJNIday05

    参考资料:

    Oracle - JNI Types and Data Structures

    获取Android应用签名的几种方式

    签名校验通过NDK实现


  • 相关阅读:
    使用PHP类库PHPqrCode生成二维码
    40+个对初学者非常有用的PHP技巧
    (高级篇)jQuery学习之jQuery Ajax用法详解
    lerna管理前端模块实践
    Node.js:深入浅出 http 与 stream
    从koa-session源码解读session本质
    Elastichsearch实践
    Linux代理搭建TinyProxy
    linux常用命令
    node前后端同构的踩坑经历
  • 原文地址:https://www.cnblogs.com/binglingziyu/p/android-ndk-jni-basic-day05.html
Copyright © 2020-2023  润新知