在我们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目录时,这个文件夹下的so库其实是在你编译so文件时,根据你之前的jni目录的文件生成的so,你会发现里面有个Android.mk的配置文件,里面默认配置了so库文件名字为app不可更改。