加密/解密 - 通过 JNI 调用 OpenSSL API





0/5 (0投票)
本博客概述了通过 OpenSSL 库将 Intel 的 AES-NI 指令集成到 Android 应用所需的步骤。
Intel® 开发者中心提供用于跨平台应用开发、平台和技术信息、代码示例以及同行专业知识的工具和操作指南,以帮助开发者进行创新并取得成功。加入我们的社区,了解 Android、物联网、Intel® RealSense™ 技术和 Windows,下载工具、访问开发套件、与志同道合的开发者交流想法,并参与黑客马拉松、竞赛、路演和本地活动。
本博客概述了通过 OpenSSL 库将 Intel 的 AES-NI 指令集成到 Android 应用所需的步骤。通过遵循此处的步骤,您将能够构建一个受益于 AES-NI 加速的 JNI 应用程序。
Intel 高级加密标准新指令 (Intel AES-NI)
Intel AES-NI 于 2008 年 3 月提出,是 Intel 微处理器 x86 指令集架构的扩展。该指令集旨在提高执行高级加密标准 (AES) 加密和解密的应用程序的性能、安全性和电源效率。
在 Android 上使用 Intel AES-NI
OpenSSL 库的 AES 算法相比原生 Java Provider 提供的算法在性能上有显著提升。这是因为该库针对 Intel 处理器进行了优化,并利用了 AES-NI 指令。下面将分步介绍如何使用 OpenSSL 提供程序加密文件。
从 Android 4.3 开始,Android 开源项目 (AOSP) 中的 OpenSSL 支持 Intel 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 function**。
- 在源 Android 项目中创建一个 jni 文件夹
- 在 jni 下创建一个预编译、包含目录。
- 将创建的 openssl 库文件夹包含在 jni 文件夹的 <OpenSSL source/include/> 中。
- 然后,在 **jni/\*.c** 中编写一个 C 函数来实现加密。之后,您需要将 **\*.a/\*.so** 和头文件复制到项目中。
- 在 jni 文件夹中加载库和 C 实现,在第 1 步创建的 Android 类函数中作为系统库。
下面的部分将介绍如何将 openssl 库包含到应用程序中并在 Java 类中调用它。
创建一个新项目,例如 Eclipse 中的 EncryptFileOpenSSL。可以通过 Eclipse(在项目浏览器中右键单击项目名称)或使用终端创建一个名为 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 提供的函数来实现您的 **encrypt/decrypt/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");
    }
现在,我们在 encodeFile.cpp 中使用 System.loadLibrary 加载的加密函数将是-
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** 在 <Application source> 中进行编译。
/<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 中存储的信息来读取平均 CPU 使用率
 
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 字节 = 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);

