做android开发,或多或少应该对ndk有些了解。大家都知道,开发android应用很多部分是使用java完成的,但是java语言使用起来虽然简单,但是也比较容易进行反编译,尽管现在网络上有很多的加密工具。那怎么保护应用的一些隐私逻辑模块(加解密)的,ndk是一个很好的选择。
ndk使用c或者cpp完成代码的编写,使用c或者cpp可以将一些模块编译为链接库(so文件),这些文件反编译起来则非常的困难,同时使用c和cpp写出的代码在执行效率上会有所提升。本文将展示使用ndk技术将字符串的简单加解密方法写进so文件中。
考虑到编码等的原因,本文中的加密解密算法方式为:java中将字符串转为为byte数组,然后通过jni调用c语言加解密函数,同时将byte数据传递给c(中间有一部类型转化,将byte数组转化为char数组),c语言对char数组进行加解密后返回。有关ndk的一些简单使用,大家可以看一些麦子学院的教程教程,本文中只对使用的一些例子进行解释。对于字符串的加解密,按照本文的方式加解密应该是一种不错的方式,使用中您可能需要修改一下c语言中的加解密函数即可。
本文中c语言对char数组的加解密很简单,对每一个char进行拆分,char占8位,高4位与低4位拆成2个数值,然后根据数值从一个长度为16的钥匙串中拿出对应字符,这些字符对应的数组记为加密后的字符串。反向解密原理相似,只需要将字符数组中每2个字符抽取,计算出加密前的数值即可。下面看一下整体ndk的使用。
使用ndk前,需要从android官网上下载ndk组件,解压后大概3G左右,建议一个新项目(AndroidNDK),将新项目目录指定在ndk解压后的根目录(这样比较方便,不这样做也可以),我们需要在新项目中额外添加的只有一个jni目录,jni目录与src,res等是同一层的。目录内需要含有的文件如下所示:
1 2 3 4 5 |
mac:AndroidNDK YD$ tree jni jni ├── Android.mk ├── Application.mk └── encrypt.c |
其中encrypt.c即是我们的加解密函数所在的位置,Android.mk与Application.mk为配置文件,内容很固定。可以看下各个文件的内容:
Application.mk
1 |
APP_ABI := armeabi,armeabi-v7a |
Application.mk中还有其他的一些配置,大家可以去官网或者google一下,常用的配置是App_ABI,指定生成对应cpu架构的库文件。
Android.mk
1 2 3 4 5 6 |
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := codeboy_encrypt LOCAL_SRC_FILES := encrypt.c LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog include $(BUILD_SHARED_LIBRARY) |
Android.mk中需要修改的内容只有LOCAL_MODULE和LOCAL_SRC_FILES,前者指定生成的连接库名称,后者指定要编译的c语言或者cpp的文件名字,其他的保持不变即可。
encrypt.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
#include<string.h> #include<jni.h> #include<android/log.h> //宏定义打印函数,使用方法 LOGI("hello") 或者 LOGI("money %d",15) #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "native", __VA_ARGS__)) #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "native", __VA_ARGS__)) const char key[] = "abcdefghijklmnop"; //16个字符 int len = 0; //计算字符对应的byte值 unsigned char getByteNumber(unsigned char first, unsigned char end) { int firstPosition = 0, endPosition = 0; int position = 0; for (; position < 16; position++) { if (key[position] == first) { firstPosition = position; } if (key[position] == end) { endPosition = position; } } return (firstPosition << 4) | (endPosition); } //加密函数 void encrypt(unsigned char p[], unsigned char res[]) { int i = 0; for (; i < len; i++) { res[2 * i] = key[p[i] / 16]; res[2 * i + 1] = key[p[i] % 16]; } } //解密函数 void decrypt(unsigned char p[], char res[]) { int i; for (i = 0; i < len; i++) { res[i] = getByteNumber(p[i * 2], p[i * 2 + 1]); } } //java中生命的native函数,函数名称格式Java_包名(点换下划线)_类名_函数名 //前两个参数JNIEnv *env, jclass this比较固定,其中第二个参数jclass代表方法是静态的,仅仅是个表示,如果方法不是静态的话,jclass换成jobject //后续的参数是函数要传进来的参数 //java中的byte数组对应jni中的jbyteArray,jni中的jbyteArray可以通过jni中的函数转换为char数组 jstring Java_me_codeboy_encrypt_EncryptUtil_encrypt(JNIEnv *env, jclass jbyteArray src) { unsigned char *buff = (char*) (*env)->GetByteArrayElements(env, src, NULL); len = (*env)->GetArrayLength(env, src); //加密后长度变为原先的2倍 unsigned char res[len * 2]; encrypt(buff, res); //此步骤很重要,标志结束 res[len * 2] = ‘\0‘; //使用完毕释放src数组,因为src数组的存在jvm中 (*env)->ReleaseByteArrayElements(env, src, buff, 0); //jni中函数将char数组转变为字符串,jni中字符串为jstring,对应java中的String jstring resStr = (*env)->NewStringUTF(env, res); return resStr; } //和加密类似 jstring Java_me_codeboy_encrypt_EncryptUtil_decrypt(JNIEnv *env, jclass jbyteArray src) { unsigned char *buff = (char*) (*env)->GetByteArrayElements(env, src, NULL); len = (*env)->GetArrayLength(env, src); //解密后长度变为原先的1/2 len = len / 2; signed char res[len]; decrypt(buff, res); //此步骤很重要,标志结束 res[len] = ‘\0‘; //使用完毕释放src数组,因为src数组的存在jvm中 (*env)->ReleaseByteArrayElements(env, src, buff, 0); jstring resStr = (*env)->NewStringUTF(env, res); return resStr; } |
这样我们的ndk相关的文件就写好了,下面在终端下切换到AndroidNDK目录下,运行命令即可:
1 |
../ndk-build |
运行结果如下:
1 2 3 4 5 6 7 8 |
mac:AndroidNDK YD$ ../ndk-build Android NDK: WARNING: APP_PLATFORM android-21 is larger than android:minSdkVersion 14 in ./AndroidManifest.xml [armeabi] Compile thumb : codeboy_encrypt <= encrypt.c [armeabi] SharedLibrary : libcodeboy_encrypt.so [armeabi] Install : libcodeboy_encrypt.so => libs/armeabi/libcodeboy_encrypt.so [armeabi-v7a] Compile thumb : codeboy_encrypt <= encrypt.c [armeabi-v7a] SharedLibrary : libcodeboy_encrypt.so [armeabi-v7a] Install : libcodeboy_encrypt.so => libs/armeabi-v7a/libcodeboy_encrypt.so |
执行完成后,我们可以看一下libs文件夹,多了一些so文件,如下:
1 2 3 4 5 6 7 8 9 |
mac:AndroidNDK YD$ tree libs libs ├── android-support-v4.jar ├── armeabi │ └── libcodeboy_encrypt.so └── armeabi-v7a └── libcodeboy_encrypt.so 2 directories, 3 files |
下面我们就开始写对应的java代码了,将调用c函数的加解密函数抽象到一个类中即可
EncryptUtil.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package me.codeboy.encrypt; public class EncryptUtil { public native static String encrypt(byte[] src); // public native static String decrypt(byte[] src); // static { System.loadLibrary("codeboy_encrypt"); } /** * 加密函数 * * @param src * @return */ public static String encrypt(String src) { return encrypt(src.getBytes()); } /** * 解密函数 * * @param src * @return */ public static String decrypt(String src) { return decrypt(src.getBytes()); } } |
注意C语言中的函数名称中的包名类名函数名要与该类统一,还有对应的链接库名称。
做好了这些以后,我们就可以使用了。在我们的Activity中简单的定义一个按钮,点击后对一个字符串进行加密打印,之后进行解密打印,Activity中的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package me.codeboy.ndk.ui; import me.codeboy.encrypt.EncryptUtil; import me.codeboy.ndk.R; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class MainActivity extends Activity { @Override protected void onCreate(Bundle setContentView(R.layout.main_ui); super.onCreate(savedInstanceState); Button btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String src = "我是玄恒,欢迎访问我的网站codeboy.me"; String res = EncryptUtil.encrypt(src); System.out.println("------>" + res); res = EncryptUtil.decrypt(res); System.out.println("--->" + res); } }); } } |
点击按钮后可以看到点击button打印的结果:
1 2 |
--->ogiijbogjikpohioieogibjcoplmimogkmkcoilpiooikolpojjhkoogiijbohjkieohlnjbohkljjgdgpgegfgcgphjcogngf --->我是玄恒,欢迎访问我的网站codeboy.me |
这样下来我们就完成了简单的加解密操作,也对ndk有了一个初步的了解。
更多文章请访问小胖轩.