通過 JNI 調用 OpenSSL 實現加密解密
Intel? Developer Zone 為跨平臺app開發提供工具和信息指引,平臺和技術信息,示例代碼,以及同行專家來幫助開發者創新和成功。加入我們的 Android, Internet of Things, Intel? RealSense? Technology, 以及 Windows社區來下載工具,獲取開發套件,與志趣相投的開發者分享觀點,參與編程馬拉松,競賽,宣傳以及本地事件。
這個博客概括了通過OpenSSL庫整合Intel的AES-NI指令到Android應用的步驟,通過下面的過程,你可以構建一個被AES-NI加速的JNI程序。
Intel 高級加密標準新操作指南(Intel AES-NI)
Intel AES-NI 在 2008 年 3 月提出,是 Inter 微處理器 x86 指令集架構的一個擴展,這個指令集的目的是提高應用程序使用高級加密標準(AES)進行加密和解密時的性能、安全性、以及執行效率。
在 Android 上使用 Intel AES-NI
OpenSSL 庫的 AES 算法比 Java 原生提供的有顯著的性能提升,這是因為 OpenSSL 庫是為 Inter 處理器優化的并且使用了AES-NI指令。下面是一個一步一步的如何使用OpenSSL來加密一個文件的描述。
Android 4.3 開始,安卓開源工程(AOSP)中的 OpenSSL 支持 Inter AES-NI,所以你僅需使用正確的配置來編譯它。另外,你可以從官方網站下載并自己編譯,然后在你的工程中直接使用 *.a/*.so,有兩種方式獲得加密庫。
如果你沒有AOSP源代碼,你可以從http://www.openssl.org/source/下載OpenSSL源代碼。使用最新的OpenSSL版本可以避免任何已知的舊版本缺陷。AOSP集成了OpenSSL庫,可以直接將它放到應用程序的jni目錄來訪問已包含的目錄。
如果你正在下載OpenSSL源代碼,并打算通過交叉編譯來創建庫,請按照下面的步驟進行:
-
下載源代碼:
</li>
wget https://www.openssl.org/source/openssl-1.0.1j.tar.gz -
編譯 ? 在控制臺運行下面的命令(注意,你需要設置NDK變量到系統的完整路徑):
</li> </ol>export NDK=~/android-ndk-r9d
export TOOL=arm-linux-androideabi
export NDK_TOOLCHAIN_BASENAME=${TOOLCHAIN_PATH}/${TOOL}
export CC=$NDK_TOOLCHAIN_BASE-gcc
export CXX=$NDK_TOOLCHAIN_BASENAME-g++
export LINK=${CXX}
export LD=$NDK_TOOLCHAIN_BASENAME-ld
export AR=$NDK_TOOLCHAIN_BASENAME-ar
export STRIP=$NDK_TOOLCHAIN_BASENAME-strip
export ARCH_FLAGS="-march=armv7-a –mfloat-abi=softfp –mfpu=vfpv3-d16"
export ARCH_LINK="-march=armv7-a –Wl, --flx-cortex-a"
export CPPFLAGS="${ARCH_FLAGS} –fpic –ffunction-sections –funwind-tables –fstack-protector –fno-strict-aliasing –finline-limited=64"
export LDFLAGS="${ARCH_LINK"}
export CXXFLAGS="${ ARCH_FLAGS} –fpic –ffunction-sections –funwind-tables –fstack-protector –fno-strict-aliasing –finline-limited=64 –frtti –fexceptions"
cd $OPENSSL_SRC_PATH
export CC="$STANDALONE_TOOCHAIN_PATH/bin/i686-linux-android-gcc –mtune=atome –march=atom –sysroot=$STANDALONE_TOOCHAIN_PATH/sysroot"
export AR=$STANDALONE_TOOCHAIN_PATH/bin/i686-linux-android-ar
export RANLIB=$STANDALONE_TOOCHAIN_PATH/bin/i686-linux-android-ranlib
./Configure android-x86 –DOPENSSL_IA32_SSE2 –DAES_ASM –DVPAES_ASM
make</pre>
接下來你可以在最上層的目錄得到 libcrypto.a 。如果你想要使用 *.so 文件,輸入 “Configure shared android-x86 ***”。
如果你有AOSP源代碼,你無需ndk工具鏈,
source build/envsetiup.sh
lunch <options>
make –j8
cd external/openssl
mm</pre>
這創建了 libcrypto.a,并放到目錄 out/host/linux_x86/bin
通過NDK在Android項目中使用OpenSSL
-
創建一個 android項目,在你最喜歡的IDE中加密文件 -- 這個例子基于 Eclipse。
</li> -
通過Android.mk將OpenSSL相關的函數聲明為 native 函數 。
</li> -
在Android項目源代碼下創建一個jni目錄。
</li> -
將之前編譯的文件,include目錄放置到jni目錄下。
</li> -
包含在jni目錄下創建的OpenSSL庫目錄<OpenSSL source/include/>。
</li> -
接下來在 jni/*.c 中編寫C函數來實現加密。完成之后,你需要拷貝 *.a/*.so 以及頭文件到項目之中。
</li> -
在步驟1中創建的作為系統庫的Android類函數中加載jni目錄下的庫和C實現。
</li> </ol>下面的部分,描述了如何在應用程序中引用OpenSSL庫,以及如何在Java類中調用它。
在Eclipse中新建一個工程,例如 EncryptFileOpenSSL 。使用Eclipse (在Project Explorer上右擊工程名,或者使用終端創建 jni目錄,以及兩個子目錄--pre-compiled 以及 include。
使用終端:
cd <workspace/of/Project>
mkdir jni/pre-compiled/
mkdir jni/include
cp $OPENSSL_PATH/libcrypto.a jni/pre-compiled
cp –L -rf $OPENSSL_PATH/include/openssl jni/include
gedit jni/Android.mk</pre>
然后將下面的內容加入到 jni/Android.mk 文件:
…
LOCAL_MODULE := static
LOCAL_SRC_FILES := pre-compiled/libcrypto.a
…
LOCAL_C_INCLUDES := include
LOCAL_STATIC_LIBRARIES := static –lcrypto
…
然后,你可以使用OpenSSL提供的函數來實現 加密/解密/SSL 函數。 為了使用Intel AES-NI, 只需要使用下面的EVP_* 系列函數, 如果CPU支持,這些函數會自動使用Intel AES-NI來加速AES加密/解密過程。例如,如果你要編寫一個加密文件的類,使用OpenSSL,那么在.java類中的加密函數可能看起來像這樣 (這里的源代碼來自 Christopher Bird 名為 “示例代碼: 數據加密應用程序”的博客)
public long encryptFile(String encFilepath, String origFilepath) { File fileIn = new File(origFilepath); if (fileIn.isFile()) { ret = encodeFileFromJNI(encFilepath, origFilepath); } else { Log.d(TAG, "ERROR*** File does not exist:" + origFilepath); seconds = -1; } if (ret == -1) { throw new IllegalArgumentException("encrypt file execution did not succeed."); } }
/ native function available from encodeFile library / public native int encodeFileFromJNI(String fileOut, String fileIn); public native void setBlocksizeFromJNI(int blocksize); public native byte[] generateKeyFromJNI(int keysize); / To load the library that encrypts (encodeFile) on application startup. The Package manager would have alredy unpacked the library has into /data/data/com.example.openssldataencryption/lib/libencodeFile.so at installation time. / static { System.loadLibrary("crypto"); System.loadLibrary("encodeFile"); }</pre>
現在,我們使用 System.loadLibrary 加載的 encodeFile.cpp 中的加密函數將會是-
int encodeFile(const char filenameOut, const char filenameIn) {
int ret = 0; int filenameInSize = strlen(filenameIn)sizeof(char)+1; int filenameOutSize = strlen(filenameOut)sizeof(char)+1;
char filename[filenameInSize]; char encFilename[filenameOutSize];
// create key, if it's uninitialized int seedbytes = 1024;
memset(cKeyBuffer, 0, KEYSIZE );
if (!opensslIsSeeded) { if (!RAND_load_file("/dev/urandom", seedbytes)) { //__android_log_print(ANDROID_LOG_ERROR, TAG, "Failed to seed OpenSSL RNG"); return -1; } opensslIsSeeded = 1; }
if (!RAND_bytes((unsigned char *)cKeyBuffer, KEYSIZE )) { //__android_log_print(ANDROID_LOG_ERROR, TAG, "Faled to create OpenSSSL random integers: %ul", ERR_get_error); }
strncpy(encFilename, filenameOut, filenameOutSize); encFilename[filenameOutSize-1]=0; strncpy(filename, filenameIn, filenameInSize); filename[filenameInSize-1]=0;
EVP_CIPHER_CTX *e_ctx = EVP_CIPHER_CTX_new();
FILE orig_file, enc_file;
printf ("filename: %s\n" ,filename ); printf ("enc filename: %s\n" ,encFilename ); orig_file = fopen( filename, "rb" ); enc_file = fopen ( encFilename, "wb" );
unsigned char encData, origData; int encData_len = 0; int len = 0; int bytesread = 0;
/* ENCRYPT */ //if (!(EVP_EncryptInit_ex(e_ctx, EVP_aes_256_cbc(), NULL, key, iv ))) { if (!(EVP_EncryptInit_ex(e_ctx, EVP_aes_256_cbc(), NULL, cKeyBuffer, iv ))) { ret = -1; printf( "ERROR: EVP_ENCRYPTINIT_EX\n"); } // go through file, and encrypt if ( orig_file != NULL ) { origData = new unsigned char[aes_blocksize]; encData = new unsigned char[aes_blocksize+EVP_CIPHER_CTX_block_size(e_ctx)]; // potential for encryption to be 16 bytes longer than original
printf( "Encoding file: %s\n", filename);
bytesread = fread(origData, 1, aes_blocksize, orig_file); // read bytes from file, then send to cipher while ( bytesread ) {
if (!(EVP_EncryptUpdate(e_ctx, encData, &len, origData, bytesread))) { ret = -1; printf( "ERROR: EVP_ENCRYPTUPDATE\n"); } encData_len = len;
fwrite(encData, 1, encData_len, enc_file ); // read more bytes bytesread = fread(origData, 1, aes_blocksize, orig_file); } // last step encryption if (!(EVP_EncryptFinal_ex(e_ctx, encData, &len))) { ret = -1; printf( "ERROR: EVP_ENCRYPTFINAL_EX\n"); } encData_len = len;
fwrite(encData, 1, encData_len, enc_file );
// free cipher EVP_CIPHER_CTX_free(e_ctx);
// close files printf( "\t>>\n");
fclose(orig_file); fclose(enc_file); } else { printf( "Unable to open files for encoding\n"); ret = -1; return ret; } return ret; }</pre>
然后在應用源碼中使用ndk-build進行編譯。
/<path to android-ndk>/ndk-build APP_ABI=x86
復制/<PATH\TO\OPENSSL>/include/openssl 目錄到</PATH\to\PROJECT\workspace>/jni/.
*.so/*.a 應該放在 /</PATH\to\PROJECT\workspace>/libs/x86/. 或者 /</PATH\to\PROJECT\workspace>/libs/armeabi/.
用來進行加密/解密的encode.cpp 文件應該放在 </PATH\to\PROJECT\workspace>/jni/.
性能分析
下面的函數可以用來分析加密一個文件的cpu使用率,內存使用和時間花費。再一次,這些源碼出自Christopher Bird的博客。
CPU占用率
下面的代碼可以幫助我們了解cpu平均使用率 (利用存儲在/proc/stat的信息)
public float readCPUusage() { try { RandomAccessFile reader = new RandomAccessFile("/proc/stat", "r"); String load = reader.readLine(); String[] toks = load.split(" "); long idle1 = Long.parseLong(toks[5]); long cpu1 = Long.parseLong(toks[2]) + Long.parseLong(toks[3]) + Long.parseLong(toks[4]) + Long.parseLong(toks[6])+ Long.parseLong(toks[7]) +Long.parseLong(toks[8]); try { Thread.sleep(360); } catch (Exception e) { }
reader.seek(0); load = reader.readLine(); reader.close(); toks = load.split(" "); long idle2 = Long.parseLong(toks[5]); long cpu2 = Long.parseLong(toks[2]) + Long.parseLong(toks[3])+ Long.parseLong(toks[4]) + Long.parseLong(toks[6]) + Long.parseLong(toks[7]) + ong.parseLong(toks[8]); return (float) (cpu2 - cpu1) / ((cpu2 + idle2) - (cpu1 + idle1)); } catch (IOException ex) { ex.printStackTrace(); } return 0; }</pre>
Memory占用率
下面的代碼讀取可用的系統內存.
Memory Info 是一個Android API,它允許我們查詢可用的內存信息.
由于, 1024 Bytes = 1 kB & 1024 kB = 1 MB. 因此, 轉換可用內存到 MB - 1024*1024 == 1048576
public long readMem(ActivityManager am) { MemoryInfo mi = new MemoryInfo(); am.getMemoryInfo(mi); long availableMegs = mi.availMem / 1048576L; return availableMegs; }
定時分析
start = System.currentTimeMillis(); // Perform Encryption. stop = System.currentTimeMillis(); seconds = (stop - start);
本文地址:http://www.oschina.net/translate/encryption-decryption-invoking-openssl-api-through-jni-calls
原文地址:http://www.codeproject.com/Articles/867764/Encryption-Decryption-invoking-OpenSSL-API-through
</div>本文由用戶 y37f 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
-