保存密钥我们可以将密钥写在如下三个地方:
1.java source code;
2.gradle中,使用BuildConfig读取;
3.写在gradle properties中,再在build gradle中读取,同第二种方法;
上述三种方法可以用且方便为什么我们要将密钥写在C/C++中呢!大家都知道写在Android代码中很容易让别人通过反编译进行读取;这样就存在很大的安全隐患;
可能有网友会问写在C/C++中别人也可以将我们的.so包拿到(将Apk解包就能拿到),然后自己声明native方法,load库,然后调用native方法,那么我们即使写在C/C++中也是白做功夫,所以今天我们来改进一下在C/C++中保存读取密钥。
改进步骤如下:
首先我们要在native代码里面,先验证一下应用的签名是否是我们的,如果是,才返回正确的密钥。
1.获取签名唯一字符串
在任意一个Activity中调用如下方法,可以得到签名的字符串。
public void getSignInfo() {
try {
PackageInfo packageInfo = getPackageManager().getPackageInfo(
getPackageName(), PackageManager.GET_SIGNATURES);
Signature[] signs = packageInfo.signatures;
Signature sign = signs[0];
System.out.println(sign.toCharsString());
} catch (Exception e) {
e.printStackTrace();
}
}
2.修改native方法的声明,传入Context对象。
public native String nativeMethod(Context context);
然后修改C++代码添加逻辑:
#include <jni.h>
#include <stdio.h>
#include <string.h>
#ifdef __cplusplus
extern "C"{
#endif
static jclass contextClass;
static jclass signatureClass;
static jclass packageNameClass;
static jclass packageInfoClass;
/**
之前生成好的签名字符串
*/
const char* RELEASE_SIGN = "第1步,生成好的字符串";
/*
根据context对象,获取签名字符串
*/
const char* getSignString(JNIEnv *env,jobject contextObject) {
jmethodID getPackageManagerId = (env)->GetMethodID(contextClass, "getPackageManager","()Landroid/content/pm/PackageManager;");
jmethodID getPackageNameId = (env)->GetMethodID(contextClass, "getPackageName","()Ljava/lang/String;");
jmethodID signToStringId = (env)->GetMethodID(signatureClass, "toCharsString","()Ljava/lang/String;");
jmethodID getPackageInfoId = (env)->GetMethodID(packageNameClass, "getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
jobject packageManagerObject = (env)->CallObjectMethod(contextObject, getPackageManagerId);
jstring packNameString = (jstring)(env)->CallObjectMethod(contextObject, getPackageNameId);
jobject packageInfoObject = (env)->CallObjectMethod(packageManagerObject, getPackageInfoId,packNameString, 64);
jfieldID signaturefieldID =(env)->GetFieldID(packageInfoClass,"signatures", "[Landroid/content/pm/Signature;");
jobjectArray signatureArray = (jobjectArray)(env)->GetObjectField(packageInfoObject, signaturefieldID);
jobject signatureObject = (env)->GetObjectArrayElement(signatureArray,0);
return (env)->GetStringUTFChars((jstring)(env)->CallObjectMethod(signatureObject, signToStringId),0);
}
jstring Java_[ClassAPackage]_A_nativeMethod(JNIEnv *env,jobject thiz,jobject contextObject) {
const char* signStrng = getSignString(env,contextObject);
if(strcmp(signStrng,RELEASE_SIGN)==0)//签名一致 返回合法的 api key,否则返回错误
{
return (env)->NewStringUTF("你的密钥");
}else
{
return (env)->NewStringUTF("error");
}
}
/**
利用OnLoad钩子,初始化需要用到的Class类.
*/
JNIEXPORT jint JNICALL JNI_OnLoad (JavaVM* vm,void* reserved){
JNIEnv* env = NULL;
jint result=-1;
if(vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
return result;
contextClass = (jclass)env->NewGlobalRef((env)->FindClass("android/content/Context"));
signatureClass = (jclass)env->NewGlobalRef((env)->FindClass("android/content/pm/Signature"));
packageNameClass = (jclass)env->NewGlobalRef((env)->FindClass("android/content/pm/PackageManager"));
packageInfoClass = (jclass)env->NewGlobalRef((env)->FindClass("android/content/pm/PackageInfo"));
return JNI_VERSION_1_4;
}
#ifdef __cplusplus
}
#endif
getSignString方法也许看起来很复杂,如果熟悉java反射的Api的话,其实很类似,就是拿到方法的Id,调用方法。
上述这种改进的话就能防止别人破译你的密钥。