NDK学习系列--第三篇
简介
目前是NDK开发的入门阶段,以前对C++有一点点基础,不过因为多年没有编写过C++程序了,所以现在也是希望可以边通过项目和实现功能来熟悉。今天学习了如何通过NDK来实现网络请求参数加密和签名校验,可能大部分人都没有了解过参数加密和签名校验,或者平时只是使用普通的对称加密算法对参数进行加密,比如通过MD5对参数加密,发送到服务器,服务端再对参数进行解密,得到实际的参数。这样的方式和裸奔基本上没啥区别,别人只要反编译拿到你的加密key值就可以随便破解了。可能小公司对安全方面的考虑没这么重视,但是大公司肯定会精密设置加密和安全校验,如百度地图、支付宝等。
为什么要用NDK来实现加密和校验呢?
首先最重要的一点是第一篇所说的基于安全性的考虑,防止代码被反编译。我们对参数的加密方式,如果是用java来写,被人反编译后就可以看到我们的逻辑,这样别人只要把代码一copy,然后就可以随便调用的我们服务接口,对我们的后台服务进行攻击或者篡改数据,这样是非常不安全的。其次通过NDK来实现,一套代码可以拿到多个平台去使用,不止Android可以使用,这样可以大大提高可用性和效率。
有加密了为啥还要检验呢?这二者缺一不可,有了加密,并不代表就是安全的,别人可以看你是如何调用加密接口,只要会调用接口,那别人的参数也一样是合法的,发送到服务端也是有效的请求。因此我们需要校验调用我们的接口的人是不是我们所希望的人。就好比一些秘密行动需要接头暗号一般,确认对方是自己人后才能和他接头,否则不理睬。业内的做法一般是校验报名和签名,使用过百度地图SDK的人应该都知道我们需要通过报名和签名来配置应用的百度地图key,这个key需要注册到项目的清单文件中。我个人猜测百度就是在so库去获取的应用的包名和签名,通过他们定好的规则设成key,然后拿这个key和你在清单文件里面的注册的key进行对比,一致的话说明你是正确的接头人,可以去使用百度地图SDK服务。不一致则会报错,使用不了百度地图。
实现过程
1、进行合法性校验。
代码如下:
// 校验签名
// 校验成功的变量
static int is_verify =0 ;
ava_cn_novate_ndk_day01_SignatureUtils_signatureVerify(JNIEnv *env, jclass type, jobject context) {
// TODO
// 1. 获取包名
jclass j_clz = env->GetObjectClass(context);
// getPackageName:表示方法名;
// ()Ljava/lang/String; :表示方法参数;
// ():表示无参构造方法
jmethodID j_mid = env->GetMethodID(j_clz, "getPackageName" , "()Ljava/lang/String;");
jstring j_package_name = (jstring) env->CallObjectMethod(context, j_mid);
// 2. 比对包名是否一样
const char * c_package_name = env -> GetStringUTFChars(j_package_name, NULL);
// strcmp方法: 字符串比较
if(strcmp(c_package_name, PACKAGE_NAME) !=0){
return;
}
// 打印log , %s表示占位符 打印结果:包名一致: cn.novate.ndk.day01
__android_log_print(ANDROID_LOG_ERROR , "JNI_TAG" , "包名一致: %s" , c_package_name);
// 3. 获取签名
// 3.1 获取PackageManager
j_mid = env -> GetMethodID(j_clz, "getPackageManager" , "()Landroid/content/pm/PackageManager;");
jobject package_manager = env->CallObjectMethod(context, j_mid);
// 3.2 获取PackageInfo
j_clz = env->GetObjectClass(package_manager);
j_mid = env->GetMethodID(j_clz, "getPackageInfo" , "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
// 0x00000040 = 64
jobject package_info = env->CallObjectMethod(package_manager, j_mid, j_package_name, 0x00000040);
// 3.3 获取 signature数组
j_clz = env -> GetObjectClass(package_info);
jfieldID j_fid = env->GetFieldID(j_clz, "signatures" , "[Landroid/content/pm/Signature;");
jobjectArray signatures = (jobjectArray) env->GetObjectField(package_info, j_fid);
// 3.4 获取signature[0]
jobject signature_first = env->GetObjectArrayElement(signatures,0);
// 3.5 调用signatures[0].toCharsString()
j_clz = env->GetObjectClass(signature_first);
j_mid = env->GetMethodID(j_clz, "toCharsString" , "()Ljava/lang/String;");
jstring j_signature_str = (jstring) env->CallObjectMethod(signature_first, j_mid);
const char * c_signature_str = env->GetStringUTFChars(j_signature_str,NULL);
// 比较字符串
// 比较签名是否相等,如果不相等,则直接 return;
if(strcmp(c_signature_str, APP_SIGNATURE) !=0){
return;
}
// 打印下日志
__android_log_print(ANDROID_LOG_ERROR , "JNI_TAG" , "签名校验成功:%s" , c_signature_str);
// 4. 比对签名是否一样,如果签名一样,就让is_verify = 1 ;
is_verify =1 ;
}
这个方法就是用来校验包名和签名,通过反射调用Java类的接口和熟悉,拿到包名和签名,然后进行校验,我这里只是进行普通的校验,将签名写死在了代码中。如果是在公司的项目中,这个签名应该放在后台服务中才是安全的。