NDK开发:保存App私密信息成So库,使用加载动态连接库获取私密信息

在我们App开发中总会有一些私密信息需要保存到App客户端,即使App有Progrund做代码混淆,但是安卓有很多反编译的方法获取App原代码,这些私密信息还是不够安全。
下面我们就来实现一个把私密信息保存成so库,然后让App动态加载,或许这些私密信息。
1)首先,安装安卓的NDK开发环境:
比较完整的NDK开发步骤:
我也按照网上的配置了很多次生成so文件的时候总是会出现问题,编译可以通过,但是在build/intermediates/ndk目录下总是没有办法得到我想要的so文件库。
所以我采用第二种方式,通过ndk-build命令生成so库。
1)首先你要先配置ndk环境变量,然后通过ndk-build命令测试是否成功。
2)在main目录下面新建一个jni目录,主要包含四个文件
1)通过cd 进入到项目的java目录,然后通过javah c文件的引用生成的头文件。

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

#ifndef _Included_com_example_lynnlee_ndkdemo_MainActivity
#define _Included_com_example_lynnlee_ndkdemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_lynnlee_ndkdemo_MainActivity
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_lynnlee_ndkdemo_MainActivity_stringFromJNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

2)实现头文件的.c或者.cpp文件。

#include <jni.h>
#include "native-lib.h"

JNIEXPORT jstring JNICALL
Java_com_example_lynnlee_ndkdemo_MainActivity_stringFromJNI(JNIEnv *env, jobject instance) {
    return (*env)->NewStringUTF(env, "LynnLee");
}

这个实现文件,就是拷贝头文件的方法,然后实现这个方法得到的,所以需要引入头文件。
3)配置文件 Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := native-lib

LOCAL_SRC_FILES := native-lib.c

include $(BUILD_SHARED_LIBRARY)

4)Application.mk配置文件。

APP_ABI := all

这四个文件配置完成之后,下面就是生成so文件的关键步骤了。
首先你要cd 到你项目的jni根目录下面,然后执行ndk-build命令,就会在你的项目main根目录下生成两个文件夹,一个是libs,这个文件下就是我们需要的各个架构平台的so文件;还有一个是obj文件,这个文件我们可以直接删除。
虽然你拿到你想要的so文件,但是你发现你在代码里面像下面一样调用会报错:

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("hello");
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    public native String stringFromJNI();
}

发现程序运行成功之后直接闪退了!!!

 Process: com.example.myapplication, PID: 4378
    java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.myapplication-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.myapplication-1/lib/arm64, /system/lib64, /vendor/lib64]]] couldn't find "libhelloJni.so"
        at java.lang.Runtime.loadLibrary0(Runtime.java:984)
        at java.lang.System.loadLibrary(System.java:1562)
        at com.example.myapplication.MainActivity.<clinit>(MainActivity.java:16)
        at java.lang.Class.newInstance(Native Method)
        at android.app.Instrumentation.newActivity(Instrumentation.java:1079)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2598)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2767)
        at android.app.ActivityThread.-wrap12(ActivityThread.java)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1514)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:163)
        at android.app.ActivityThread.main(ActivityThread.java:6221)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:794)

程序找不到我们的so文件?
我们直接拷贝需要libs文件包名为jniLibs,再次运行就可以了。
这里我们就通过ndk把一个私密信息放在了so文件,然后通过代码加载获取。使用场景有和服务器通讯保存服务器公钥,还有就是第三方SDK的appId等标识,还有微信支付等一些敏感商户信息。但是这样做也不能做到绝对的安全。因为如果别人拿到了你的源代码,他可以通过运行你的项目,调试获取你的信息。所有这个看开发场景吧,最好的方式还是要保存到服务器 端。
记录一下NDK开发遇到的问题:
1)生成so文件的方法生成头文件的时候一定是有调用的类,比如我要在一个普通的java类里面去定义native方法,那么我取的时候,也是需要用到这个实现类去调用方法。
要在Activity里面调用,那么头文件制作需要指定这个具体的调用类。要不然具体调用的时候就会报错,方法没有实现。

public class NdkTestActivity extends AppCompatActivity {
    static {
        System.loadLibrary("jnitest");
    }
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e(">>>>>>>>>>>>","native:"+getStringFromNative());

    }
    public native String getStringFromNative();
}

2,提示项目调用了c++代码,但是没有配置环境。
在项目的gradle.properties目录下加入Android.useDeprecatedNdk=true
如果还是不行,需要在build.gradle文件的buildTypes目录下设置如下代码:

sourceSets {
            main {
                jni.srcDirs = []
            }
        }

3.项目找不到so库文件异常,导致报这个错的原因是因为我们没有设置项目的lib资源目录,异常如下:

 java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.fenjiread.learner-1/base.apk"],nativeLibraryDirectories=[/data/app/com.fenjiread.learner-1/lib/arm64, /system/lib64, /vendor/lib64]]] couldn't find "libjnitest.so"

SourceSets目录结构是固定的Java的标准项目目录布局

android {
    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
            jniLibs.srcDirs = ['libs']
        }
}

那么如何进行修改?

sourceSets {
    main {
        java {
            srcDir 'src/java' // 指定源码目录
        }
        resources {
            srcDir 'src/resources' //资源目录
        }
    }
}

这里找不到我们的libs目录,是因为我们定义的libs并不是系统的libs,我们的libs是自动生成在main包结构目录下的,所以我们需要在gradle里面写死libs的绝对目录。

sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/libs']
        }
    }

这样就解决so文件找不到的问题了。
4,在生成so文件的时候,cd 到jni目录下,执行ndk-build命令,会在build/intermediates目录下生成一个jinlibs文件夹,如果这个文件夹下面有so文件,说明你的程序运行是没有问题的,如果没有就是说明文件没有被找到或者编译出错了。如下图:
NDK开发:保存App私密信息成So库,使用加载动态连接库获取私密信息
当你发现有个ndk目录时,这个文件夹下的so库其实是在你编译so文件时,根据你之前的jni目录的文件生成的so,你会发现里面有个Android.mk的配置文件,里面默认配置了so库文件名字为app不可更改。