從 6.0 開始,Google 要求不要使用系統的 OpenSSL,請見:https://developer.android.com...。所以,請不要再使用本文介紹的方法,請自行交叉編譯 OpenSSL 或者使用別人編譯好的版本。
2017年3月注html
因爲Java較爲容易被反編譯,所以把一些重要代碼放在so文件中成爲了一個代價不過高的選擇。雖然so文件依舊能夠反編譯,但對so進行逆向分析的門檻則要比分析Java字節碼的門檻高出很多。不少安全相關的代碼都依賴OpenSSL,然而網絡上在NDK中使用OpenSSL的教程並很少見,通過一天的探索,我終於能夠成功在NDK中調用OpenSSL了。本文將以調用OpenSSL中的HMAC算法爲例,介紹如何使用Gradle配置NDK並在NDK中使用OpenSSL。java
2015年7月,Google發佈了新的Gradle插件,提供了對NDK的支持,今後,編寫NDK程序再也不須要編寫Android.mk
文件,也再也不須要使用ndk-build
腳本,只須要在Gradle中簡單的配置一下,便可方便的編譯程序了。android
目前,新的插件仍處在beta版本,本文選用當前時間(2016年3月2日)最新的0.6.0-beta5
做介紹。要獲取最新的更新,請訪問這裏。git
從傳統的Android Gradle插件遷移到新的插件並不困難,只須要修改原有目錄結構中的三個文件便可。以以下目錄爲例:github
. ├── app/ │ ├── build.gradle │ └── src/ ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── local.properties └── settings.gradle
須要修改的文件包括兩個build.gradle
、gradle-wrapper.properties
和local.properties
文件。算法
分別來看對它們的修改:api
每一個版本的插件都只支持特定的Gradle版本,所以請務必對照上文給出的連接填寫正確的版本。安全
./local.properties
網絡
須要在文件中指定ndk.dir
屬性,指向NDK
的路徑。app
./gradle/wrapper/gradle-wrapper.properties
這個文件定義了Gradle的版本,這裏須要使用gradle-2.10
,所以須要把最後一行的版本替換掉。
./build.gradle
這裏定義了構建時使用的插件,須要替換爲com.android.tools.build:gradle-experimental:0.6.0-beta5
。
./app/build.gradle
這個文件變化較大,主要的變化包括:
插件名由原來的com.android.application
變爲com.android.model.application
。
全部的配置放置在model { }
塊中。
minSdkVersion
和targetSdkVersion
都須要配置它們的apiLevel
屬性。
下面是一個完整的示例:
apply plugin: 'com.android.model.application' model { android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { applicationId "com.example.openssltest" minSdkVersion.apiLevel 14 targetSdkVersion.apiLevel 23 versionCode 1 versionName "1.0" } ndk { moduleName = "openssl-jni" platformVersion = 14 ldFlags.add("-lcrypto") abiFilters.add("armeabi-v7a") } } } dependencies { compile 'com.android.support:appcompat-v7:23.1.1' }
這個配置中,多出了ndk
部分,下面就來解釋一下這部分的配置:
moduleName
決定了編譯出來的庫的名稱,這個選項是必須的。
platformVersion
指定了NDK的platform版本,這裏使用14是由於minSdkVersion
使用14。
ldFlags
指定了連接時的參數,因爲本例中只用到了HMAC算法,所以這裏添加了對crypto
,若是還須要TLS的支持,這裏須要改成ldFlags.addAll(["-lcrypto", "-lssl"])
。
abiFilters
裏指定了abi的版本armeabi-v7a
,
Android裏已經內置了OpenSSL,但NDK中並無提供相應的庫。只須要把OpenSSL的.so
文件放在NDK中便可:
$adb pull /system/lib/libssl.so /myndk/platforms/android-14/arch-arm/usr/lib $adb pull /system/lib/libcrypto.so /myndk/platforms/android-14/arch-arm/usr/lib
而後把OpenSSL的頭文件放在 /myndk/platforms/android-14/arch-arm/usr/include
目錄中便可。
編寫代碼請參考JNI的文檔,下面給出一個調用HMAC-SHA256
的實現:
#include <jni.h> #include <openssl/hmac.h> #ifdef __cplusplus extern "C" { #endif jbyteArray Java_com_example_openssltest_MainActivity_hmacSha256(JNIEnv *env, jobject obj, jbyteArray content) { unsigned char key[] = {0x6B, 0x65, 0x79}; unsigned int result_len; unsigned char result[EVP_MAX_MD_SIZE]; // get data from java array jbyte *data = env->GetByteArrayElements(content, NULL); size_t dataLength = env->GetArrayLength(content); HMAC(EVP_sha256(), key, 3, (unsigned char *) data, dataLength, result, &result_len); // release the array env->ReleaseByteArrayElements(content, data, JNI_ABORT); // the return value jbyteArray return_val = env->NewByteArray(result_len); env->SetByteArrayRegion(return_val, 0, result_len, (jbyte *) result); return return_val; } #ifdef __cplusplus } #endif
在Java中調用也很容易,只須要引用build.gradle
中指定的庫便可:
public native byte[] hmacSha256(byte[] data); static { System.loadLibrary("openssl-jni"); }
https://github.com/terro/andr...
須要注意的是,這裏只是一個簡單的DEMO,不要直接在項目中保存密鑰之類的信息。