通过 JNI 调用 OpenSSL 实现加密解密

来源:开源中国社区 作者:oschina
  

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变量到系统的完整路径):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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工具链,

1
2
3
4
5
6
7
8
9
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实现。

下面的部分,描述了如何在应用程序中引用OpenSSL库,以及如何在Java类中调用它。

在Eclipse中新建一个工程,例如 EncryptFileOpenSSL 。使用Eclipse (在Project Explorer上右击工程名,或者使用终端创建 jni目录,以及两个子目录--pre-compiled 以及 include。

使用终端:

1
2
3
4
5
6
7
8
9
10
11
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 名为 “示例代码: 数据加密应用程序”的博客)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 中的加密函数将会是-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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进行编译。

1
/<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的信息)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

1
2
3
4
5
6
public long readMem(ActivityManager am) {
            MemoryInfo mi = new MemoryInfo();
            am.getMemoryInfo(mi);
            long availableMegs = mi.availMem / 1048576L;
            return availableMegs;
      }

定时分析

1
2
3
4
start = System.currentTimeMillis();
// Perform Encryption.
stop = System.currentTimeMillis();
seconds = (stop - start);

 


时间:2015-03-15 09:31 来源:开源中国社区 作者:oschina 原文链接

好文,顶一下
(0)
0%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量