25.Android Studio下Ndk开发(参数加密解决方案)
网络请求通过http传递到后台,如果不对数据做加密处理的话,很容易会被抓包,此时,app就是很不安全的,被截取到接口地址和参数后容易被攻击。今天我要分享的就是如何提高网络接口安全性的解决方案。
之前做的项目是采取直接在java层对参数进行加密,加密方式也有很多,RSA加密,MD5加密,AES加密,DES加密,Base64加密等等,具体介绍可以参考这里 Android中的加密方法(http://www.cnblogs.com/whoislcj/p/5470095.html),这种方式在一定程度上可以提高数据的安全性,但是深入来看,我们的加密方式对外暴露出来,当app被反编译时,对方可以拿到我们的代码,可以看到我们加密的方式,这样一来,会更加容易让对方找到破解密文的方法,因为在目前所有加密方式中,既具备实用性又具备绝对安全性的方法是不存在的。所以我们是否可以做到加密方式也对外不可见呢,或者如果不能做到绝对不可见,是否可以大大的提高对方破解密文的难度。这就是今天要做的,通过jni将加密方法打包到so库中,防止被放编译,算是在这些加密算法的上面加一层壳,这里以md5加密为例。
so库破解的难度之大,远远超过破解混淆后的apk,所以jni是解决安全性隐患的一个切入点。
创建CMakeLists文件,配置相关的内容
#参数加密 cmake_minimum_required(VERSION 3.4.1) find_library( log-lib log ) add_library( encrypt SHARED src/main/cpp/encrypt.cpp src/main/cpp/md5.cpp) # 将预构建库与本地库相连 target_link_libraries( encrypt ${log-lib} )
EncryptUtils
package com.app.rzm.utils; import android.content.Context; /** * ndk实现参数加密 */ public class EncryptUtils { static { System.loadLibrary("encrypt"); } public static String encrypt(Context context, String param){ checkSignature(context); return encryptNative(context,param); } /** * 对一个字符串进行加密 * @param context * @param param * @return */ private static native String encryptNative(Context context, String param); /** * 校验app签名 * @param context */ private static native void checkSignature(Context context); }
调用方式
public class TestParamsEncryptActivity extends AppCompatActivity { private TextView mText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test_params_encrypt); mText = (TextView) findViewById(R.id.text); //拿到签名 try { PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES); Signature[] signatures = packageInfo.signatures; LogUtils.d("signature:"+signatures[0].toCharsString()); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } //将网络请求中的http参数拼接成这样的字符串username=renzhenming&password=123456 //然后将这个参数字符串进行加密 String params = EncryptUtils.encrypt(this,"username=renzhenming&password=123456"); //作为参数给到服务器,服务器也生成同样的密文,然后将加密的字符串进行比较 mText.setText(params); } }
接下来是关键代码,在c++中实现md5加密, * 加密解密的过程:
md5.h
#ifndef MD5_H #define MD5_H typedef struct { unsigned int count[2]; unsigned int state[4]; unsigned char buffer[64]; }MD5_CTX; #define F(x,y,z) ((x & y) | (~x & z)) #define G(x,y,z) ((x & z) | (y & ~z)) #define H(x,y,z) (x^y^z) #define I(x,y,z) (y ^ (x | ~z)) #define ROTATE_LEFT(x,n) ((x << n) | (x >> (32-n))) #define FF(a,b,c,d,x,s,ac) { \ a += F(b, c, d) + x + ac; \ a = ROTATE_LEFT(a, s); \ a += b; \ } #define GG(a,b,c,d,x,s,ac) { \ a += G(b, c, d) + x + ac; \ a = ROTATE_LEFT(a, s); \ a += b; \ } #define HH(a,b,c,d,x,s,ac) { \ a += H(b, c, d) + x + ac; \ a = ROTATE_LEFT(a, s); \ a += b; \ } #define II(a,b,c,d,x,s,ac) { \ a += I(b, c, d) + x + ac; \ a = ROTATE_LEFT(a, s); \ a += b; \ } void MD5Init(MD5_CTX *context); void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen); void MD5Final(MD5_CTX *context, unsigned char digest[16]); void MD5Transform(unsigned int state[4], unsigned char block[64]); void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len); void MD5Decode(unsigned int *output, unsigned char *input, unsigned int len); #endif
md5.cpp
#include "md5.h" #include "string" unsigned char PADDING[] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; //在逆向代码的时候,需要关注下面的特征值 void MD5Init(MD5_CTX *context) { context->count[0] = 0; context->count[1] = 0; context->state[0] = 0x67452301; context->state[1] = 0xEFCDAB89; context->state[2] = 0x98BADCFE; context->state[3] = 0x10325476; } void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen) { unsigned int i = 0, index = 0, partlen = 0; index = (context->count[0] >> 3) & 0x3F; partlen = 64 - index; context->count[0] += inputlen << 3; if (context->count[0] < (inputlen << 3)) context->count[1]++; context->count[1] += inputlen >> 29; if (inputlen >= partlen) { memcpy(&context->buffer[index], input, partlen); MD5Transform(context->state, context->buffer); for (i = partlen; i + 64 <= inputlen; i += 64) MD5Transform(context->state, &input[i]); index = 0; } else { i = 0; } memcpy(&context->buffer[index], &input[i], inputlen - i); } void MD5Final(MD5_CTX *context, unsigned char digest[16]) { unsigned int index = 0, padlen = 0; unsigned char bits[8]; index = (context->count[0] >> 3) & 0x3F; padlen = (index < 56) ? (56 - index) : (120 - index); MD5Encode(bits, context->count, 8); MD5Update(context, PADDING, padlen); MD5Update(context, bits, 8); MD5Encode(digest, context->state, 16); } void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len) { unsigned int i = 0, j = 0; while (j < len) { output[j] = input[i] & 0xFF; output[j + 1] = (input[i] >> 8) & 0xFF; output[j + 2] = (input[i] >> 16) & 0xFF; output[j + 3] = (input[i] >> 24) & 0xFF; i++; j += 4; } } void MD5Decode(unsigned int *output, unsigned char *input, unsigned int len) { unsigned int i = 0, j = 0; while (j < len) { output[i] = (input[j]) | (input[j + 1] << 8) | (input[j + 2] << 16) | (input[j + 3] << 24); i++; j += 4; } } void MD5Transform(unsigned int state[4], unsigned char block[64]) { unsigned int a = state[0]; unsigned int b = state[1]; unsigned int c = state[2]; unsigned int d = state[3]; unsigned int x[64]; MD5Decode(x, block, 64); FF(a, b, c, d, x[0], 7, 0xd76aa478); FF(d, a, b, c, x[1], 12, 0xe8c7b756); FF(c, d, a, b, x[2], 17, 0x242070db); FF(b, c, d, a, x[3], 22, 0xc1bdceee); FF(a, b, c, d, x[4], 7, 0xf57c0faf); FF(d, a, b, c, x[5], 12, 0x4787c62a); FF(c, d, a, b, x[6], 17, 0xa8304613); FF(b, c, d, a, x[7], 22, 0xfd469501); FF(a, b, c, d, x[8], 7, 0x698098d8); FF(d, a, b, c, x[9], 12, 0x8b44f7af); FF(c, d, a, b, x[10], 17, 0xffff5bb1); FF(b, c, d, a, x[11], 22, 0x895cd7be); FF(a, b, c, d, x[12], 7, 0x6b901122); FF(d, a, b, c, x[13], 12, 0xfd987193); FF(c, d, a, b, x[14], 17, 0xa679438e); FF(b, c, d, a, x[15], 22, 0x49b40821); GG(a, b, c, d, x[1], 5, 0xf61e2562); GG(d, a, b, c, x[6], 9, 0xc040b340); GG(c, d, a, b, x[11], 14, 0x265e5a51); GG(b, c, d, a, x[0], 20, 0xe9b6c7aa); GG(a, b, c, d, x[5], 5, 0xd62f105d); GG(d, a, b, c, x[10], 9, 0x2441453); GG(c, d, a, b, x[15], 14, 0xd8a1e681); GG(b, c, d, a, x[4], 20, 0xe7d3fbc8); GG(a, b, c, d, x[9], 5, 0x21e1cde6); GG(d, a, b, c, x[14], 9, 0xc33707d6); GG(c, d, a, b, x[3], 14, 0xf4d50d87); GG(b, c, d, a, x[8], 20, 0x455a14ed); GG(a, b, c, d, x[13], 5, 0xa9e3e905); GG(d, a, b, c, x[2], 9, 0xfcefa3f8); GG(c, d, a, b, x[7], 14, 0x676f02d9); GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); HH(a, b, c, d, x[5], 4, 0xfffa3942); HH(d, a, b, c, x[8], 11, 0x8771f681); HH(c, d, a, b, x[11], 16, 0x6d9d6122); HH(b, c, d, a, x[14], 23, 0xfde5380c); HH(a, b, c, d, x[1], 4, 0xa4beea44); HH(d, a, b, c, x[4], 11, 0x4bdecfa9); HH(c, d, a, b, x[7], 16, 0xf6bb4b60); HH(b, c, d, a, x[10], 23, 0xbebfbc70); HH(a, b, c, d, x[13], 4, 0x289b7ec6); HH(d, a, b, c, x[0], 11, 0xeaa127fa); HH(c, d, a, b, x[3], 16, 0xd4ef3085); HH(b, c, d, a, x[6], 23, 0x4881d05); HH(a, b, c, d, x[9], 4, 0xd9d4d039); HH(d, a, b, c, x[12], 11, 0xe6db99e5); HH(c, d, a, b, x[15], 16, 0x1fa27cf8); HH(b, c, d, a, x[2], 23, 0xc4ac5665); II(a, b, c, d, x[0], 6, 0xf4292244); II(d, a, b, c, x[7], 10, 0x432aff97); II(c, d, a, b, x[14], 15, 0xab9423a7); II(b, c, d, a, x[5], 21, 0xfc93a039); II(a, b, c, d, x[12], 6, 0x655b59c3); II(d, a, b, c, x[3], 10, 0x8f0ccc92); II(c, d, a, b, x[10], 15, 0xffeff47d); II(b, c, d, a, x[1], 21, 0x85845dd1); II(a, b, c, d, x[8], 6, 0x6fa87e4f); II(d, a, b, c, x[15], 10, 0xfe2ce6e0); II(c, d, a, b, x[6], 15, 0xa3014314); II(b, c, d, a, x[13], 21, 0x4e0811a1); II(a, b, c, d, x[4], 6, 0xf7537e82); II(d, a, b, c, x[11], 10, 0xbd3af235); II(c, d, a, b, x[2], 15, 0x2ad7d2bb); II(b, c, d, a, x[9], 21, 0xeb86d391); state[0] += a; state[1] += b; state[2] += c; state[3] += d; }
客户端通过定义的规则将参数加密后,将密文和铭文参数同时传递到服务器,服务器收到参数进行解析,使用同样的加密算法将参数加密,然后对比此次得到的密文和客户端传递的密文是否相同,如果相同说明数据安全,没有被篡改,如果不同,则表示数据改变,不再发送数据到客户端
将加密方法打包到so库中的好处就是可以防止对方反编译看到我们的加密条件,如果对方不知道我们是如何加密的,也就可以在一定程度上防止数据泄漏,但是只是单纯的这样做并不能保证绝对的安全,比如,我不需要知道你是怎么加密的,只需要反编译apk后得到几个信息1.你应用的包名,2.你的so库,3.你的native方法
的完整类名和方法名(native方法不能被混淆,混淆后无法使用,所以可以得到),只要得到这三个信息,我就可以创建包名相同方法名相同的一个应用,把so放进去,然后就可以绕过密钥检查,轻松的调用你的接口了。
解决这个问题的方法就是在so库中加入签名验证,当调用加密方法对操作参数的时候,验证此时应用签名是否是我们本应用的,如果不是,则表示当前应用是伪应用,直接返回, 防止上边那种恶意调用接口情况的出现。对签名做校验,也就是只允许指定的应用可以使用,类似在微信支付中,也有在官方管理后台申请和配置应用的的签名和包名,否则就禁止使用,签名和包名必须得要一致。
com_app_rzm_utils_EncryptUtils.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_app_rzm_utils_Encryptils */ #ifndef _Included_com_app_rzm_utils_EncryptUtils #define _Included_com_app_rzm_utils_EncryptUtils #ifdef __cplusplus extern "C" { #endif /* * Class: com_app_rzm_utils_Encryptils * Method: encryptNative * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_app_rzm_utils_EncryptUtils_encryptNative (JNIEnv *, jclass, jobject ,jstring); JNIEXPORT void JNICALL Java_com_app_rzm_utils_EncryptUtils_checkSignature (JNIEnv *, jclass, jobject); #ifdef __cplusplus } #endif #endif
encrypt.cpp
#include "com_app_rzm_utils_EncryptUtils.h" #include "md5.h" #include <string> #include <android/log.h> using namespace std; #define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"renzhenming",FORMAT,##__VA_ARGS__); #define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"renzhenming",FORMAT,##__VA_ARGS__); //我们加密的方式是对参数进行md5加密,在md5加密之前还有一层加密,就是对参数字符串进行改造 //在字符串前加上自定义的key值,然后去掉字符串后边两位字符串,这个规则按需定制,增加破解的难度 #define MD5_KEY "renzhenming" //签名校验是否通过,否返回-1 static int signature_verify = -1; //app包名 static char* PACKAGE_NAME = "com.app.rzm"; //app签名,在这里配置我们app的正式签名,在so库中,可以保证安全性 static char* APP_SIGNATURE = "308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b3009060355040613025553301e170d3137303332333033323030305a170d3437303331363033323030305a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330819f300d06092a864886f70d010101050003818d00308189028181008658a9a532d1c5e8c5a1a78c61535636220f73deb9b36d0912e2b6b1c50f5ed7eccb5cd8e0e4b2fd192d983fa15aeef6be5e5258e809b3fbad538fb68d1c78ebbdd89080664d707e9731c706386a45242a1e3a9e4819789bf832206a2bb7a45b663b6deb9be153ca4fe77b26ca1c43d85cbc20465cc6046f1e1dc16bbc65fe310203010001300d06092a864886f70d010105050003818100213550aa14811a7a407e7eb148b9cb50709c2c84185340b4c52f22caff07fd2e4a79e2814dc5a16fbd7a4b3ec638574eb5ee6baf7537b69aed6529a594bf556f1e0f073884739271f3e2572c4c174031b547846212643ae57bb35e8157c65b3760e37fc3c74f4e24daf3d91086d6ddd7a3f1e54b69ad235a6eb6c5524ce24800"; /** * @param env * @param jclazz * @param jparam * @return */ JNIEXPORT jstring JNICALL Java_com_app_rzm_utils_EncryptUtils_encryptNative (JNIEnv *env, jclass jclazz,jobject context,jstring jparam){ if(signature_verify == -1){ return env->NewStringUTF("EncryptUtils--> signature check err"); } const char *param = env->GetStringUTFChars(jparam,NULL); //在参数头位置加上MD5_KEY,然后去掉后面两位字符串 string signature_str(param); //insert(int p0, const char *s);在p0位置插入字符串s signature_str.insert(0,MD5_KEY); signature_str = signature_str.substr(0,signature_str.length()-2); //md5加密 MD5_CTX *ctx = new MD5_CTX(); MD5Init(ctx); MD5Update(ctx,(unsigned char *)signature_str.c_str(),signature_str.length()); unsigned char digest[16]; MD5Final(ctx, digest); int i = 0; char szMd5[32] = {0}; for(i = 0;i< 16 ; i++){ LOGI("EncryptUtils--> szMd5[%d]:%s",i,szMd5); //最终生成32位,不足前面补一位0 //x 表示以十六进制形式输出 ,02 表示不足两位,前面补0输出;出过两位,不影响 sprintf(szMd5,"%s%02x",szMd5,digest[i]); } env->ReleaseStringUTFChars(jparam,param); return env->NewStringUTF(szMd5); } JNIEXPORT void JNICALL Java_com_app_rzm_utils_EncryptUtils_checkSignature (JNIEnv *env, jclass jclazz, jobject context){ //1.获取包名 通过Context的getPackageName方法获取 //获取Context对象的class jclass context_class = env->GetObjectClass(context); //获取getPackageName的方法id jmethodID context_method_id = env->GetMethodID(context_class,"getPackageName","()Ljava/lang/String;"); //调用getPackageName方法 jstring package_name = (jstring)env->CallObjectMethod(context,context_method_id); //转换为char* const char *c_package_name = (char *)env->GetStringUTFChars(package_name,NULL); LOGI("EncryptUtils--> package name:%s\n",c_package_name); //2.对比包名 if(strcmp(c_package_name,PACKAGE_NAME)){ LOGI("EncryptUtils--> package name check err"); return; } //3.获取签名(通过下边这种方式) //PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES); //Signature[] signatures = packageInfo.signatures; //LogUtils.d("signature:"+signatures[0].toCharsString()); //获取Context中的getPackageManager方法id jmethodID get_package_manager_method_id = env->GetMethodID(context_class,"getPackageManager","()Landroid/content/pm/PackageManager;"); //从Context中通过调用getPackageManager获取PackageManager对象 jobject package_manager = env->CallObjectMethod(context,get_package_manager_method_id); //获取PackageManager对象的class jclass package_manager_class = env->GetObjectClass(package_manager); //获取PackageManager对象中的getPackageInfo方法id jmethodID get_package_info_method_id = env->GetMethodID(package_manager_class,"getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"); //调用PackageInfo中的getPackageInfo方法获取PackageInfo对象(PackageManager.GET_SIGNATURES=64) jobject package_manager_info = env->CallObjectMethod(package_manager,get_package_info_method_id,package_name,64); //获取PackageInfo的class,获取它的属性的时候要用到 jclass package_info_class = env->GetObjectClass(package_manager_info); //获取PackageInfo中的signatures属性的fieldid jfieldID signatures_field_id = env->GetFieldID(package_info_class,"signatures","[Landroid/content/pm/Signature;"); //获取PackageInfo中的signatures属性 jobjectArray signatures_arrary = (jobjectArray) env->GetObjectField(package_manager_info, signatures_field_id); //获取数组中[0]位置的元素 jobject signature = env->GetObjectArrayElement(signatures_arrary,0); //获取String的class jclass signature_class = env->GetObjectClass(signature); //获取String中的toCharsString方法的methodid jmethodID to_chars_string_method_id = env->GetMethodID(signature_class,"toCharsString","()Ljava/lang/String;"); //调用String的toCharsString jstring signature_string = (jstring) env->CallObjectMethod(signature, to_chars_string_method_id); //转换为char* const char * signature_char = env->GetStringUTFChars(signature_string,NULL); LOGI("EncryptUtils--> current app signature:%s\n",signature_char); LOGI("EncryptUtils--> real app signature:%s\n",APP_SIGNATURE); //4.对比签名 if(strcmp(signature_char,APP_SIGNATURE) == 0){ signature_verify = 1; LOGI("EncryptUtils--> signature_verify success:%d\n",signature_verify); }else{ signature_verify = -1; LOGI("EncryptUtils--> signature_verify check failed:%d\n",signature_verify); } }
总结一下:
我们通过使用纯c++代码实现md5加密,将加密实现方式打包成so库,提高反编译的难度,另外在md5加密之外我们还设置了另一层加密规则,对参数字符串头尾进行处理,双层加密,确保数据的安全性。在加密手段之外,再进行app包名和签名的校验,从而保证so库只能在我们自己的app中使用。三层保护,这样一来,相信即便是遇到逆向工程师,要破解我们的app也是有一定难度的。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
RN Exception: Before building your project, you need to accept the lic...
异常 * What went wrong: A problem occurred configuring project ':app'. > You have not accepted the license agreements of the following SDK components: [Android SDK Platform 23, Android SDK Build-Tools 23.0.1]. Before building your project, you need to accept the license agreements and comp lete the installation of the missing components using the Android Studio SDK Man ager. Alternatively, to learn how to transfer the license agreements from one workstat ion to another, go to http://d.android.c...
- 下一篇
自己用到的Android 双服务保活(适配8.0)
最近开发的时候,测试小伙伴经常来找我,“为什么咱家程序放到后台,聊了会qq就得重启了呢?”我脑门一亮,“稍等,一会给你”。然后我就进入了程序流氓(进程保活)之旅。 对于进程保活,其实吧,现在对于MIUI、EMUI等等许多高度定制的系统并没有100%的保活方案,该死还是死掉,但是做了一定的操作,还是可以适当的提高存活的。如下就是我用到的保活方案。 1、启动软件的时候激活本地服务和远程服务 startService (new Intent (this, MainService.class)); startService (new Intent (this, RemoteService.class)); 2、本地服务代码 /** * content:后台运行的服务 * Actor:韩小呆 * Time:2018/5/3 */ public class MainService extends Service { MyBinder binder; MyConn conn; @Override public IBinder onBind(Intent intent) { return binder...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7设置SWAP分区,小内存服务器的救世主
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS8编译安装MySQL8.0.19
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS7安装Docker,走上虚拟化容器引擎之路