做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 this, 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 this, 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 savedInstanceState) { 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有了一個初步的瞭解。
更多文章請訪問小胖軒.