Android使用C/C++来保存密钥

本文主要介绍如何通过native方法调用取出密钥,以替代原本直接写在Java中,或写在gradle脚本中的不安全方式。

为什么要这么做

如果需要在本地存储一个密钥串,典型的方式有

  1. 直接写在java source code中
  2. 写在gradle脚本中,使用BuildConfig读取
  3. 写在gradle.properties中,再到gradle脚本中读取,后面同第二点
  4. 使用native方法,读取存放在C/C++中的字段

本质上来讲方式1,2,3没有什么区别。1为硬编码,2可以做到在不同的BuildType使用不同的密钥,3将配置写到脚本之外,方便管理查看。

然而,在项目编译之后,方式1,2,3都会把密钥直接替换到字节码文件中,对于反编译如此方便的Android来说,无疑是将密钥拱手让人。

因此,将密钥放在难以反编译的C/C++代码中,是一个解决的办法。

怎么做

java怎么调用C/C++方法

如果想详细的明白以下步骤,请查阅JNI相关的资料,此处仅列出大概步骤。

  1. 下载ndk
  2. 在类中声明native方法。
  public class A {

      public native String nativeMethod();

  }
  1. 在项目根目录下新建一个名为jni的目录,并在其中新建三个文件,分别为:
  • Android.mk (名字固定)
  • Application.mk (名字固定)
  • Project.cpp (名字随意)
  1. Android.mk

文件的内容如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := project
LOCAL_SRC_FILES := Project.cpp

include $(BUILD_SHARED_LIBRARY)

除了LOCAL_MODULELOCAL_SRC_FILES之外,其它都是固定的。前者是这个库的名称,后者是cpp文件的路径。

  1. Application.mk

文件的内容如下:

APP_ABI := all

意思是生成所有平台的so库。

  1. Project.cpp
#include <jni.h>
#include <stdio.h>
#include <string.h>

#ifdef __cplusplus
extern "C"{
#endif

jstring Java_[ClassAPackage]_A_nativeMethod(JNIEnv *env,jobject thiz) {

    // 返回密钥
    return (env)->NewStringUTF("你的密钥");

}

#ifdef __cplusplus
}
#endif

ClassAPackage为类A在java中的包名全称,并将分隔的.改成_

  1. 以上就把native的代码写好了,在第一步下载好的NDK里面,使用解压后目录下的一个叫ndk-build的程序。cdjni目录下,执行ndk-build,如果执行无误的话,会如下图所示。
ndk-build
  1. 执行完上一步之后,会生成一个与jni同级的目录libs,将libs下的文件拷贝到app/src/main/jniLibs目录下。

  2. 在类A中,加入以下静态语句块,引入编译好的native库。

static {
    System.loadLibrary("project");
}

这里的"project"就是在第4步中的LOCAL_MODULE的值。

  1. 到了这一步,就可以拿到native代码中保存的值了。

有啥问题不

肯定有啊。

试想,如果有人将我们的.so包拿到了(把apk解包就能拿到),然后自己声明native方法,load本地库,然后调用native方法,那么我们做的这么多是不是都白费了?是的,白费了。所以我们需要改进。

如何改进

有什么东西,只有你自己知道,并且有的,但是别人不能模仿的?--应用签名。

那么,我们在native代码里面,先验证一下应用的签名是否是我们的,如果是,才返回正确的密钥。

  1. 获取签名唯一字符串
    BuildVariants切换到release,也就是使用生产版本的签名文件,然后将下面的代码粘贴至任意一个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();
       }
   }
  1. 修改native方法的声明,传入Context对象。
public native String nativeMethod(Context context);
  1. 修改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,调用方法。

以上就是本文的讨论内容,有些技术细节没有深入介绍,请自行查阅相关资料。
如果有不同的方式,欢迎讨论

QQ: 2424334647

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容