通過 JNI 調用 OpenSSL 實現加密解密

y37f 9年前發布 | 62K 次閱讀 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

    </li>

  2. 編譯 ? 在控制臺運行下面的命令(注意,你需要設置NDK變量到系統的完整路徑):

    </li> </ol>

    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 &ndash;mfloat-abi=softfp &ndash;mfpu=vfpv3-d16"

            export ARCH_LINK="-march=armv7-a &ndash;Wl, --flx-cortex-a"

            export CPPFLAGS="${ARCH_FLAGS} &ndash;fpic &ndash;ffunction-sections &ndash;funwind-tables &ndash;fstack-protector &ndash;fno-strict-aliasing &ndash;finline-limited=64"

            export LDFLAGS="${ARCH_LINK"}

            export CXXFLAGS="${ ARCH_FLAGS} &ndash;fpic &ndash;ffunction-sections &ndash;funwind-tables &ndash;fstack-protector &ndash;fno-strict-aliasing &ndash;finline-limited=64 &ndash;frtti &ndash;fexceptions"

            cd $OPENSSL_SRC_PATH

            export CC="$STANDALONE_TOOCHAIN_PATH/bin/i686-linux-android-gcc &ndash;mtune=atome &ndash;march=atom &ndash;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 &ndash;DOPENSSL_IA32_SSE2 &ndash;DAES_ASM &ndash;DVPAES_ASM

          make</pre>


    接下來你可以在最上層的目錄得到 libcrypto.a 。如果你想要使用 *.so 文件,輸入 “Configure shared android-x86 ***”

    如果你有AOSP源代碼,你無需ndk工具鏈,

    source build/envsetiup.sh

          lunch <options>

          make &ndash;j8

          cd external/openssl

          mm</pre>

    這創建了 libcrypto.a,并放到目錄 out/host/linux_x86/bin

    通過NDK在Android項目中使用OpenSSL

    1. 創建一個 android項目,在你最喜歡的IDE中加密文件 -- 這個例子基于 Eclipse。

      </li>

    2. 通過Android.mk將OpenSSL相關的函數聲明為 native 函數

      </li>

    3. 在Android項目源代碼下創建一個jni目錄。

      </li>

    4. 將之前編譯的文件,include目錄放置到jni目錄下。

      </li>

    5. 包含在jni目錄下創建的OpenSSL庫目錄<OpenSSL source/include/>。

      </li>

    6. 接下來在 jni/*.c 中編寫C函數來實現加密。完成之后,你需要拷貝 *.a/*.so 以及頭文件到項目之中。

      </li>

    7. 在步驟1中創建的作為系統庫的Android類函數中加載jni目錄下的庫和C實現。

      </li> </ol>

      下面的部分,描述了如何在應用程序中引用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</pre>

      然后將下面的內容加入到 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");     }</pre>

      現在,我們使用 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; }</pre>

      然后在應用源碼中使用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;       }</pre>

      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);
       本文由用戶 y37f 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
       轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
       本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!