65.9K
CodeProject 正在变化。 阅读更多。
Home

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

2015年2月12日

CPOL

4分钟阅读

viewsIcon

25860

本博客概述了通过 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 源代码进行交叉编译并自行创建库,请执行以下操作:

  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 function**。
  3. 在源 Android 项目中创建一个 jni 文件夹
  4. 在 jni 下创建一个预编译、包含目录。
  5. 将创建的 openssl 库文件夹包含在 jni 文件夹的 <OpenSSL source/include/> 中。
  6. 然后,在 **jni/\*.c** 中编写一个 C 函数来实现加密。之后,您需要将 **\*.a/\*.so** 和头文件复制到项目中。
  7. 在 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);
© . All rights reserved.