通过 JNI 调用 OpenSSL 实现加密解密 已翻译 100%

oschina 投递于 2015/02/13 06:33 (共 6 段, 翻译完成于 03-08)
阅读 7858
收藏 86
3
加载中

Intel® Developer Zone 为跨平台app开发提供工具和信息指引,平台和技术信息,示例代码,以及同行专家来帮助开发者创新和成功。加入我们的 Android, Internet of Things, Intel® RealSense™ Technology, 以及 Windows社区来下载工具,获取开发套件,与志趣相投的开发者分享观点,参与编程马拉松,竞赛,宣传以及本地事件。

这个博客概括了通过OpenSSL库整合Intel的AES-NI指令到Android应用的步骤,通过下面的过程,你可以构建一个被AES-NI加速的JNI程序。

气质舞王尼古拉斯赵四
翻译于 2015/03/05 13:49
2

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,有两种方式获得加密库。

气质舞王尼古拉斯赵四
翻译于 2015/03/05 11:14
2

如果你没有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 &ndash;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实现。

gones945
翻译于 2015/03/05 20:29
1

下面的部分,描述了如何在应用程序中引用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 &ndash;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");
    }
gones945
翻译于 2015/03/07 11:22
1

现在,我们使用 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&apos;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的博客。

气质舞王尼古拉斯赵四
翻译于 2015/03/06 11:42
1

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);
瑞新
翻译于 2015/02/15 20:09
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(2)

kuwork
kuwork
${TOOLCHAIN_PATH}这个在哪里定义了?
BugScanner
BugScanner
这是因为 OpenSSL 库是为 Inter 处理器优化的并且使用了AES-NI指令。
返回顶部
顶部