转载

通过 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源代码,并打算通过交叉编译来创建库,请按照下面的步骤进行:

  1. 下载源代码:

    wget https://www.openssl.org/source/openssl-1.0.1j.tar.gz

  2. 编译 ‒ 在控制台运行下面的命令(注意,你需要设置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

  1. 创建一个 android项目 ,在你最喜欢的IDE中加密文件 -- 这个例子基于 Eclipse。

  2. 通过Android.mk将OpenSSL相关的函数声明为 native 函数

  3. 在Android项目源代码下创建一个jni目录。

  4. 将之前编译的文件,include目录放置到jni目录下。

  5. 包含在jni目录下创建的OpenSSL库目录<OpenSSL source/include/>。

  6. 接下来在 jni/*.c 中编写C函数来实现加密。完成之后,你需要拷贝  *.a/*.so 以及头文件到项目之中。

  7. 在步骤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占用率

下面的代码可以帮助我们了解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占用率

下面的代码读取可用的系统内存.

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);
正文到此结束
Loading...