zoukankan      html  css  js  c++  java
  • 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实现


  • 相关阅读:
    容器跨主机网络通信学习笔记(以Flannel为例)
    Kubernetes控制器Job和CronJob
    记一次使用Flannel插件排错历程
    Kubernetes控制器Deployment
    Kubernetes如何通过StatefulSet支持有状态应用?
    react18 来了,我 get 到...
    gojs 实用高级用法
    vuecli3 vue2 保留 webpack 支持 vite 成功实践
    calibre 报错 This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem. 解决
    unable to recognize "*.yaml": no matches for kind "RoleBinding" in version "rbac.authorization.k8s.io/v1beta1"
  • 原文地址:https://www.cnblogs.com/binglingziyu/p/android-ndk-jni-basic-day05.html
Copyright © 2011-2022 走看看