Android本地数据安全尝试(下)——JNI

0x00

前两篇,我们谈到了使用SQLCipher和Conceal对本地数据进行加密。由于都两种方法都采用了对称加密,因此我们需要自己管理加密的秘钥。这时你会发现,虽然对我们的数据进行了加密,但是我们却引入了新的问题。我们的加密方法很容易通过反编译apk获取到,那么,我们就需要安全的维护这个秘钥了。但遗憾的是,本地数据存储方式我们都已经讲述,并没有一种一劳永逸的安全保存方法,那么,我们的秘钥存在哪里合适呢?这个时候你可能会想到本地不行那我们存到服务器上吧,通过https进行传输。这样当然可以,通过一定的算法为每个人配置一个秘钥,需要的时候请求网络获取,然后对本地数据进行解密。但是这样也存在一个问题:本地保存的数据如果不联网就无法打开。那么,还有更好的方案吗?今天给大家介绍JNI。

0x01

NDK,JNI对于刚接触android开发的攻城狮来说是有较大的门槛的,但是为了实现我们更安全的保存数据,他可能是不错的选择。反编译过别人家app的你可能都遇到过这样的情况:一个个没无法查看代码逻辑的so文件。这些文件我们可以使用java代码调用,实现一些我们不知道内部逻辑但是会给我们一个结果功能。那么,我们把密码放到这些so中就可以更进一步提高我们本地数据的安全级别。

下面通过一个加单的demo来看看JNI的实现。

首先创建一个工具类:

public class CipherUtil {

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

    public static native String getCipherKey();

}

在这个类文件上点击右键,使用我们之前配置的javah工具生成头文件。之后我们会在和java目录同级的jni文件下看到一个.h文件,如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_ttdevs_ndk_CipherUtil */

#ifndef _Included_com_ttdevs_ndk_CipherUtil
#define _Included_com_ttdevs_ndk_CipherUtil
#ifdef __cplusplus
extern "C" {
#endif
/* * Class: com_ttdevs_ndk_CipherUtil * Method: getCipherKey * Signature: ()Ljava/lang/String; */
JNIEXPORT jstring JNICALL Java_com_ttdevs_ndk_CipherUtil_getCipherKey
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

然后在h文件的同级新建一个C++文件(右键>New>C/C++ Source file),内容如下:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "QiniuConfig.h"

#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

#include "com_ttdevs_ndk_CipherUtil.h"

JNIEXPORT jstring JNICALL Java_com_ttdevs_ndk_CipherUtil_getCipherKey(JNIEnv *env, jclass)
{
    return (*env).NewStringUTF("Hello World! getCipherKey");
}

我们还需要创建两个文件,一个叫Android.mk,另一个叫Application.mk,他们都在jni目录下。

  • Android.mk:
# http://developer.android.com/intl/zh-tw/ndk/guides/android_mk.html

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := Cipher
LOCAL_SRC_FILES := Cipher.cpp
LOCAL_LDLIBS += -llog

include $(BUILD_SHARED_LIBRARY)
  • Application.mk
# http://developer.android.com/intl/zh-tw/ndk/guides/application_mk.html

# APP_STL := stlport_static
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -std=c++11
APP_CFLAGS += -Wno-error=format-security

APP_ABI := all

还没有完,我们还需要修改当前Project或者Module的gradle.build文件:

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"

        ndk {
            moduleName "ndkutil"
        }
    }

    sourceSets.main {
        jni.srcDirs = []
        jniLibs.srcDir "libs"
    }
...
}

完成这些,我们就可以进行编译了。在当前Project(Module)上点击右键,使用之前配置的ndk-build工具进行编译,如果没有问题,我们会在libs目录下看到生产的so文件。好了,最后我们可以编写测试代码了:在java直接调用刚才创建的CipherUtil即可:

Log.d(">>>>>", CipherUtil.getCipherKey());

运行上面代码,我们可以在log中看到输出的字符串:

Hello World! getCipherKey

0x02

上述demo中,我们只是简单的返回一个字符串,要实现更安全,我们可以将此方法写的更复杂,比如获取app的签名,获取设备的硬件信息进行复杂的组合,以保障最终生成的秘钥的唯一性和安全性(更难伪造),这里有一个demo可以参考。

写到这里,可能又有人会问到:其实so文件也不是很全,可以通过对汇编的分析得到里面的代码逻辑。当然,高手是可以做到对so文件进行分析的,但是so还是可以阻隔大部分的反编译人员。如果我们能把getCipherKey实现的更好,也会增加破解的成本。另外,so还有一个被盗用的问题,就是别人直接调用我们的so,这个问题也可以通过一定的代码逻辑来避免。之后会继续讲解。

最后再说一点,对于秘钥,我们最终还会被载入我们的内存,如果直接dump我们的内存,会是一个人什么样的结果呢?

你可能感兴趣的:(android,jni,数据存储,反编译,数据安全)