Intel® Developer Zone 为跨平台app开发提供工具和信息指引,平台和技术信息,示例代码,以及同行专家来帮助开发者创新和成功。加入我们的 Android , Internet of Things , Intel® RealSense™ Technology , 以及 Windows 社区来下载工具,获取开发套件,与志趣相投的开发者分享观点,参与编程马拉松,竞赛,宣传以及本地事件。
这个博客概括了通过OpenSSL库整合Intel的AES-NI指令到Android应用的步骤,通过下面的过程,你可以构建一个被AES-NI加速的JNI程序。
Intel AES-NI 在 2008 年 3 月提出,是 Inter 微处理器 x86 指令集架构的一个扩展,这个指令集的目的是提高应用程序使用高级加密标准(AES)进行加密和解密时的性能、安全性、以及执行效率。
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源代码,并打算通过交叉编译来创建库,请按照下面的步骤进行:
下载源代码:
wget https://www.openssl.org/source/openssl-1.0.1j.tar.gz
编译 ‒ 在控制台运行下面的命令(注意,你需要设置NDK变量到系统的完整路径):
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
接下来你可以在最上层的目录得到 libcrypto.a 。如果你想要使用 *.so 文件,输入 “ Configure shared android-x86 ***” 。
如果你有AOSP源代码,你无需ndk工具链,
source build/envsetiup.sh lunch <options> make –j8 cd external/openssl mm
这创建了 libcrypto.a,并放到目录 out/host/linux_x86/bin
通过NDK在Android项目中使用OpenSSL
创建一个 android项目 ,在你最喜欢的IDE中加密文件 -- 这个例子基于 Eclipse。
通过Android.mk将OpenSSL相关的函数声明为 native 函数 。
在Android项目源代码下创建一个jni目录。
将之前编译的文件,include目录放置到jni目录下。
包含在jni目录下创建的OpenSSL库目录<OpenSSL source/include/>。
接下来在 jni/*.c 中编写C函数来实现加密。完成之后,你需要拷贝 *.a/*.so 以及头文件到项目之中。
在步骤1中创建的作为系统库的Android类函数中加载jni目录下的库和C实现。
下面的部分,描述了如何在应用程序中引用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
然后将下面的内容加入到 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"); }
现在,我们使用 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; }
然后在应用源码中使用 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平均使用率 (利用存储在/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; }
下面的代码读取可用的系统内存.
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);