Android密码保护
由于公司其中一个app在与服务端交互时要求加密,当时想想还挺简单的就用AES加密就ok可后面仔细想想AES加密需要用到密码而这个密码直接写到程序中别人反编译你app之后就能找到你的密码拿去使用,那咋办呢?有没有办法让别人拿不到我的密码呢?当然有啦,我们可以把密码写到jni里面这样别人就没有办法直接拿到我的密码,但是别人把我的so库拷贝到他自己的项目中不就可以一样使用了,还好办法还是有的,在获取密码之前我们可以获取当前app的签名信息,然后做一下对比(当前我们自己可以先获取一下签名信息可以写死在jni里面)如果匹配不上那么久认为不是咋们自己的app在调用就返回一个错误的密码给调用者也不提示错误玩死他们(O(∩_∩)O哈哈哈~)一下视具体实现方法
注:使用androidstudio开发,阅读前请先了解一下什么是jni开发。
- 先创一个用于获取AES密码的类来加载jni
package com.ocmgr.util;
import android.content.Context;
public class MYJNI {
static {
System.loadLibrary("mylib");
}
public native static String GetAESKey(Context context);
}
- 在项目中创建jni文件夹加入一下文件
//----------------
// jni接口 文件名mylib.h
//----------------
#include <jni.h>
#ifndef _Included_COM_OCMGR_UTIL_MYJNI
#define _Included_COM_OCMGR_UTIL_MYJNI
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL Java_com_ocmgr_util_MYJNI _GetAESKey
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
// 文件名:mylib.c
#include <jni.h>
#include <android/log.h>
#include <string.h>
#include <stdio.h>
#define LOG_TAG "MYJHI"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#ifndef _Included_COM_OCMGR_UTIL_MYJHI
#define _Included_COM_OCMGR_UTIL_MYJHI
#ifdef __cplusplus
extern "C" {
#endif
jstring ErrorAESKey = "需要自己改写(错误的密码)";
jstring SuccessAESKey = "正确的密码";
jstring SuccessMD5;
jint SuccessHashCode = 正确的HashCode值;
// 字节流转换为十六进制字符串
void ByteToHexStr(const unsigned char* source, char* dest, int sourceLen)
{
short i;
unsigned char highByte, lowByte;
for (i = 0; i < sourceLen; i++)
{
highByte = source[i] >> 4;
lowByte = source[i] & 0x0f;
highByte += 0x30;
if (highByte > 0x39)
dest[i * 2] = highByte + 0x07;
else
dest[i * 2] = highByte;
lowByte += 0x30;
if (lowByte > 0x39)
dest[i * 2 + 1] = lowByte + 0x07;
else
dest[i * 2 + 1] = lowByte;
}
return;
}
JNIEXPORT jstring JNICALL GetSignature(JNIEnv *env, jobject jObj,jobject context) {
// 获得Context类
jclass cls = (*env)->GetObjectClass(env, context);
// 得到getPackageManager方法的ID
jmethodID mid = (*env)->GetMethodID(env, cls, "getPackageManager", "()Landroid/content/pm/PackageManager;");
// 获得应用包的管理器
jobject pm = (*env)->CallObjectMethod(env, context, mid);
// 得到getPackageName方法的ID
mid = (*env)->GetMethodID(env, cls, "getPackageName", "()Ljava/lang/String;");
// 获得当前应用包名
jstring packageName = (jstring)(*env)->CallObjectMethod(env, context, mid);
// 获得PackageManager类
cls = (*env)->GetObjectClass(env, pm);
// 得到getPackageInfo方法的ID
mid = (*env)->GetMethodID(env, cls, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
// 获得应用包的信息
jobject packageInfo = (*env)->CallObjectMethod(env, pm, mid, packageName, 0x40); //GET_SIGNATURES = 64;
// 获得PackageInfo 类
cls = (*env)->GetObjectClass(env, packageInfo);
// 获得签名数组属性的ID
jfieldID fid = (*env)->GetFieldID(env, cls, "signatures", "[Landroid/content/pm/Signature;");
// 得到签名数组
jobjectArray signatures = (jobjectArray)(*env)->GetObjectField(env, packageInfo, fid);
// 得到签名
jobject sign = (*env)->GetObjectArrayElement(env, signatures, 0);
// 获得Signature类
cls = (*env)->GetObjectClass(env, sign);
// 得到toCharsString方法的ID([B)Ljava/lang/String;
mid = (*env)->GetMethodID(env, cls, "toByteArray", "()[B");
// 返回当前应用签名信息
jbyteArray signaturesByte = (jbyteArray)(*env)->CallObjectMethod(env, sign, mid);
// MessageDigest localMessageDigest = MessageDigest.getInstance("MD5");
jclass class_MessageDigest = (*env)->FindClass(env, "java/security/MessageDigest");
jmethodID tem_method = (*env)->GetStaticMethodID(env, class_MessageDigest, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;");
jobject obj_md5 = (*env)->CallStaticObjectMethod(env, class_MessageDigest, tem_method, (*env)->NewStringUTF(env, "md5"));
// localMessageDigest.update(localSignature.toByteArray());
//tem_class = (*env)->GetObjectClass(env, obj_md5);
tem_method = (*env)->GetMethodID(env, class_MessageDigest, "update", "([B)V");// 这个函数的返回值是void,写V
(*env)->CallVoidMethod(env, obj_md5, tem_method, signaturesByte);
// localMessageDigest.digest()
tem_method = (*env)->GetMethodID(env, class_MessageDigest, "digest", "()[B");
// 这个是md5以后的byte数组,现在只要将它转换成16进制字符串,就可以和之前的比较了
jobject obj_array_sign = (*env)->CallObjectMethod(env, obj_md5, tem_method);// jni中有强转类型的概念吗
// // 这个就是签名的md5值
// String str2 = toHex(localMessageDigest.digest());
// 尝试用c写一下:http://blog.csdn.net/pingd/article/details/41945417
jsize int_array_length = (*env)->GetArrayLength(env, obj_array_sign);
jbyte* byte_array_elements = (*env)->GetByteArrayElements(env, obj_array_sign, JNI_FALSE);
char* char_result = (char*) malloc(int_array_length*2+1);// 开始没有+1,在有的情况下会越界产生问题,还是在后面补上\0比较好
// 将byte数组转换成16进制字符串,发现这里不用强转,jbyte和unsigned char应该字节数是一样的
ByteToHexStr(byte_array_elements, char_result, int_array_length);
*(char_result+int_array_length*2) = '\0';// 在末尾补\0
jstring string_result = (*env)->NewStringUTF(env, char_result);
// release
(*env)->ReleaseByteArrayElements(env, obj_array_sign, byte_array_elements, JNI_ABORT);
// 释放指针使用free
free(char_result);
return string_result;
}
JNIEXPORT char * JNICALL Jstring2CStr(JNIEnv * env,jstring jstr){
char * rtn = NULL;
jclass clsstring = (*env)->FindClass(env,"java/lang/String");
jstring strencode = (*env)->NewStringUTF(env,"UTF-8");
jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode);
jsize alen = (*env)->GetArrayLength(env,barr);
jbyte * ba = (*env)->GetByteArrayElements(env,barr,JNI_FALSE);
if(alen > 0){
rtn = (char*)malloc(alen+1); //new char[alen+1];
memcpy(rtn,ba,alen);
rtn[alen]=0;
}
(*env)->ReleaseByteArrayElements(env,barr,ba,0);
return rtn;
}
// 重点在这里
JNIEXPORT jstring JNICALL Java_com_ocmgr_util_MYJHI_GetAESKey(JNIEnv * env, jobject jObj,jobject context){
if (context == NULL){
return (*env)->NewStringUTF(env,ErrorAESKey);
}
jstring appMd5 = GetSignature(env, jObj,context);
jint hashCode = GetSignHashCode(env, jObj,context);
SuccessMD5 = (*env)->NewStringUTF(env,"正确MD5值");
if (hashCode == SuccessHashCode && (strcmp(Jstring2CStr(env,appMd5),Jstring2CStr(env,SuccessMD5)) == 0)){
return (*env)->NewStringUTF(env,SuccessAESKey);
}
return (*env)->NewStringUTF(env,ErrorAESKey);
}
JNIEXPORT jint JNICALL GetSignHashCode(JNIEnv *env, jobject jObj,jobject context) {
//Context的类
jclass context_clazz = (*env)->GetObjectClass(env, context);
// 得到 getPackageManager 方法的 ID
jmethodID methodID_getPackageManager = (*env)->GetMethodID(env, context_clazz,"getPackageManager", "()Landroid/content/pm/PackageManager;");
// 获得PackageManager对象
jobject packageManager = (*env)->CallObjectMethod(env, context,methodID_getPackageManager);
// // 获得 PackageManager 类
jclass pm_clazz = (*env)->GetObjectClass(env, packageManager);
// 得到 getPackageInfo 方法的 ID
jmethodID methodID_pm = (*env)->GetMethodID(env, pm_clazz, "getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
//
// // 得到 getPackageName 方法的 ID
jmethodID methodID_pack = (*env)->GetMethodID(env, context_clazz,"getPackageName", "()Ljava/lang/String;");
// 获得当前应用的包名
jstring application_package = (*env)->CallObjectMethod(env, context,methodID_pack);
const char *str = (*env)->GetStringUTFChars(env, application_package, 0);
//__android_log_print(ANDROID_LOG_DEBUG, "JNI", "packageName: %s\n", str);
// 获得PackageInfo
jobject packageInfo = (*env)->CallObjectMethod(env, packageManager,methodID_pm, application_package, 64);
jclass packageinfo_clazz = (*env)->GetObjectClass(env, packageInfo);
jfieldID fieldID_signatures = (*env)->GetFieldID(env, packageinfo_clazz,"signatures", "[Landroid/content/pm/Signature;");
jobjectArray signature_arr = (jobjectArray)(*env)->GetObjectField(env,packageInfo, fieldID_signatures);
//Signature数组中取出第一个元素
jobject signature = (*env)->GetObjectArrayElement(env, signature_arr, 0);
//读signature的hashcode
jclass signature_clazz = (*env)->GetObjectClass(env, signature);
jmethodID methodID_hashcode = (*env)->GetMethodID(env, signature_clazz,"hashCode", "()I");
jint hashCode = (*env)->CallIntMethod(env, signature, methodID_hashcode);
// __android_log_print(ANDROID_LOG_DEBUG, "JNI", "hashcode: %d\n", hashCode);
// //回当前应用签名信息
// // 得到toCharsString方法的ID
// jmethodID methodID_toCharsString = (*env)->GetMethodID(env, signature_clazz, "toCharsString", "()Ljava/lang/String;");
// jstring signature_info = (jstring)(*env)->CallObjectMethod(env, signature, methodID_toCharsString);
// __android_log_print(ANDROID_LOG_DEBUG, "JNI", "signature_info: %d\n", signature_info);
return hashCode;
}
#ifdef __cplusplus
}
#endif
#endif
// 在build.gradle中的defaultConfig中加入
ndk {
moduleName "mylib" //生成的so名字
abiFilters "armeabi", "armeabi-v7a", "x86"
ldLibs "log"
}
大功告成
附加:获取Android原生类的方法签名
javap -classpath (android.jar位置) -s -p (需要获取方法签名的类)
例如:javap -classpath D:\androidDev\androidSdk\platforms\android-23\android.jar -s -p android.os.Bundle
如果觉得命令行看起来不方便那么就输出到文件中
javap -classpath D:\androidDev\androidSdk\platforms\android-23\android.jar -s -p android.os.Bundle > D:\androidDev\androidProject\Bundle.txt