Android JNI NDK C++ so本地验证 获取应用签名

一.前言

最近做的应用涉及到敏感数据加密,就是数据加密后传到后台,然后后台解密出相应的值.初步用RSA公钥加密私钥解密,那么问题来了如果公钥用java文件写的话很容易泄露,所以要把公钥放在C里面,那么问题又来了,如果有人直接把SO文件拷贝过去岂不是又可以直接使用了?所以这个地方要使用so本地验证.怎么验证呢?思路是在C里面获取应用的签名,然后比对签名是否正确,如果正确才可以给java代码返回对的key值.

二.参考

有几篇非常nice的blog可以参考下.
Android Studio ndk-Jni开发详解
android so 文件存私密数据,且防止 so文件未知应用盗用
jni_获取应用包名、签名

三.集成简单的JNI

1.首先配置ndk环境

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

这个ndk可以去官网下载也可以用as下载,as下载的方法如下

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

2.配置app下的build.gradle

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png
《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

以上两个都在android{}内

3.调用c的java类

public class JNITest {
    static {
        System.loadLibrary("bbCourseLib");   //bbCourseLib 就是上一步在build.gradle中配置的moduleName so的名字
    }

    public static native String getbbCourseKeyFromC(Object contextObject);

}

4.C的头文件

先clean project 然后 rebuild project

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

然后就会看见生成classes文件夹

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

然后打开as下面的terminal 输入下面指令

《Android JNI NDK C++ so本地验证 获取应用签名》 Snip20161209_7.png

cd是指进入debug文件夹

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

javah -jni后面的路径是第3步调用c的java类的class地址,注意是debug文件夹下面的哦
《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

执行上面两句命令后会在debug文件夹下生成头文件

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

然后再main文件夹下新建一个jni文件夹,将刚刚剪切的头文件放到这个jni文件夹内

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

5.C文件

在jni文件夹新建一个C++文件 名字随便取

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

打开刚刚生成的头文件发现代码是这样的

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

复制上图红色部分到新建的c++文件中,然后加上一个返回语句

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

6.验证结果

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png
《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

最后打印结果为”哈哈哈”,说明调用c++里面的方法成功

四.C++增加验证

1.首先用java代码打log打印出打包后的app的签名

    public static String getSignature(Context context)
    {
        try {
            /** 通过包管理器获得指定包名包含签名的包信息 **/
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
            /******* 通过返回的包信息获得签名数组 *******/
            Signature[] signatures = packageInfo.signatures;
            /******* 循环遍历签名数组拼接应用签名 *******/
            return signatures[0].toCharsString();
            /************** 得到应用签名 **************/
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

2.修改C++代码,增加验证C++的完整代码如下

#include <jni.h>
#include <string.h>
#include <stdio.h>
#include "com_xxx_module_rsa_JNITest.h"

/**
 *这个key就是要用来加密传输内容的rsa的公钥
 */
const char* AUTH_KEY = "1111";

/**
 * 发布的app 签名,只有和本签名一致的app 才会返回 AUTH_KEY
 * 这个RELEASE_SIGN的值是上一步用java代码获取的值
 */
const char* RELEASE_SIGN = "xxxxxx30102020440625908300d06092a864886f70d01010b0500305a310b3009060355040613023836310e300c060355040813056875626569310e300c06035504071305777568616e310d300b060355040a130468656865310d300b060355040b130468656865310d300b0603550403130463616c6c301e170d3136303932393130303234355a170d3436303932323130303234355a305a310b3009060355040613023836310e300c060355040813056875626569310e300c06035504071305777568616e310d300b060355040a130468656865310d300b060355040b130468656865310d300b0603550403130463616c6c30820122300d06092a864886f70d01010105000382010f003082010a0282010100b508259dd7e36da221a2b5de5158e6e1f310f2b11073b359b4a3e49d80f0b8c741c167e1364e0d3054af4a084d70a7a793cc51c47818c6b862ccb11d8316cc29c9f26ae5d543288b3392d36ad7556673621d25c6ad0dc469b8355d75ead3799d7806878c1f925dad789173c8e09d196b1197a300d73ecee78228c5def17c483138db50376c5d7c1ce0aaea3e7e90b37fa8d94f3418056f25aa12522356005678065b1f559b164758dfa470c0a63f6678400abba1983db0621422eac20d2f5406d4667f6d9175084641dd12180a1a1b048836864bb0336b9ad439d5ee059562352037473460e6885ac85362a5258d9438266a07085ae8044303049b2df6a0340f0203010001a321301f301d0603551d0e04160414fcc824f06f53f2a8c8efa1b97c8fcd43f5bcfff3300d06092a864886f70d01010b050003820101004f09129e656dc9ba39082615a112ce68a08383e518dbe9fe6c12d2b67fcf4287ee7d89faadbd189f31a374be641167ec366d2ae16b82a215fef9a33f468877a1d7edc395f5224fb0a4237fdfa4e960b42a99b082f66fbc37c991b7ee0306fdfd565e432ec6e11807e6c541aad33bd221fc793484519e932b82d963694df6605e2af3d66996188cc78d9e76a2e9b5d2ab60ea481384d327f3b62efef7eab79eb6df447cfadfc6a5c0717b9b3a22592080eec1822c22380f1fa37bc0119d30878f3b8a78d93da2d3d06fd6b45f4eac4afed8fac66393b04666e6436c86f0a68e31e3013634c1a6c93ed70256f3a3bf47506baab07bfb578d48922eaeea881bacd7";


JNIEXPORT jstring JNICALL Java_com_xxx_module_rsa_JNITest_getbbCourseKeyFromC
        (JNIEnv *env, jclass jclazz, jobject contextObject){

    jclass native_class = env->GetObjectClass(contextObject);
    jmethodID pm_id = env->GetMethodID(native_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    jobject pm_obj = env->CallObjectMethod(contextObject, pm_id);
    jclass pm_clazz = env->GetObjectClass(pm_obj);
// 得到 getPackageInfo 方法的 ID
    jmethodID package_info_id = env->GetMethodID(pm_clazz, "getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jclass native_classs = env->GetObjectClass(contextObject);
    jmethodID mId = env->GetMethodID(native_classs, "getPackageName", "()Ljava/lang/String;");
    jstring pkg_str = static_cast<jstring>(env->CallObjectMethod(contextObject, mId));
// 获得应用包的信息
    jobject pi_obj = env->CallObjectMethod(pm_obj, package_info_id, pkg_str, 64);
// 获得 PackageInfo 类
    jclass pi_clazz = env->GetObjectClass(pi_obj);
// 获得签名数组属性的 ID
    jfieldID signatures_fieldId = env->GetFieldID(pi_clazz, "signatures", "[Landroid/content/pm/Signature;");
    jobject signatures_obj = env->GetObjectField(pi_obj, signatures_fieldId);
    jobjectArray signaturesArray = (jobjectArray)signatures_obj;
    jsize size = env->GetArrayLength(signaturesArray);
    jobject signature_obj = env->GetObjectArrayElement(signaturesArray, 0);
    jclass signature_clazz = env->GetObjectClass(signature_obj);
    jmethodID string_id = env->GetMethodID(signature_clazz, "toCharsString", "()Ljava/lang/String;");
    jstring str = static_cast<jstring>(env->CallObjectMethod(signature_obj, string_id));
    char *c_msg = (char*)env->GetStringUTFChars(str,0);
    //return str;
    if(strcmp(c_msg,RELEASE_SIGN)==0)//签名一致  返回合法的 api key,否则返回错误
    {
        return (env)->NewStringUTF(AUTH_KEY);
    }else
    {
        return (env)->NewStringUTF("error");
    }
}

测试发现返回的是111,然后更改c中的RELEASE_SIGN,发现返回的是error 说明代码成功.

五.删掉C文件换so

先在 app/build/intermediates/nde/debug/lib目录下找到so文件

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

复制以上的文件夹内容到libs文件加下

《Android JNI NDK C++ so本地验证 获取应用签名》 Paste_Image.png

最后删掉jni文件夹下的c文件,到此大功告成😆

六.delicious

最后的福利

    原文作者:callxkj
    原文地址: https://www.jianshu.com/p/289c0b227902
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞