android ndk的簡單應用

做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 (= 0; i < len; i++) {
        res[i] = getByteNumber(p[* 2], p[* 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有了一個初步的瞭解。


更多文章請訪問小胖軒.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章