Encryption and decryption - accessing the OpenSSL API using JNI calls

Original author: Neelima K.
  • Transfer
This blog lists steps for integrating Intel AES-NI instructions into an Android application using the OpenSSL library. By following the instructions here, you can create a JNI application that uses AES-NI acceleration.

New AES Encryption Instructions (Intel AES-NI)


Intel AES-NI instructions were introduced in March 2008 as an extension of the x86 architecture instruction set for Intel microprocessors. The purpose of this set of instructions is to increase the performance, security, and energy efficiency of applications that encrypt and decrypt data using the AES standard.

Using Intel AES-NI in Android


The AES algorithms in the OpenSSL library demonstrated significantly higher performance compared to native Java algorithms. The reason is that this library is optimized for Intel processors and uses AES-NI instructions. The following is a step-by-step description of file encryption using the OpenSSL provider.

Starting with Android 4.3, OpenSSL in AOSP supports Intel AES-NI, so you just need to compile the code with the desired configuration. You can also download it from the official website and compile it yourself, and then use the * .a / *. So file directly in your project. There are two ways to get encryption libraries.

If you do not have the AOSP source code, you can download OpenSSL here. Use the latest version to avoid all known vulnerabilities discovered in previous versions of OpenSSL. AOSP includes the integrated openssl library, which can be placed in the jni folder of the application to access the folders included in it.
If you download the openssl source code to compile and create the library yourself, use the following.
1. Download the source code:

wget https://www.openssl.org/source/openssl-1.0.1j.tar.gz .

2. Compile: run the following command in the console (note that you need to set the full path to your distribution for the NDK variable):
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-aexport 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_PATHexport 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

After that, the libcrypto.a file will appear in the top-level folder. To use the * .so file, enter Configure shared android-x86 ***.
With AOSP source code, the ndk toolchain is not needed.
source build/envsetiup.sh
      lunch <options>
      make –j8
      cd external/openssl
      mm

In this case, libcrypto.a is compiled and placed in the out / host / linux_x86 / bin directory.
Use OpenSSL via NDK in an Android project
Create an Android project to encrypt files in your favorite development environment. Here is an example with Eclipse.
  1. Declare OpenSSL related functions as native function in the Android.mk file.
  2. Create the jni folder in the original Android project.
  3. Create precompiled include folders inside the jni folder.
  4. Include the OpenSSL library folder created in <OpenSSL source / include /> in the jni folder.
  5. Then implement encryption by writing the C function in jni / *. C. After that, you need to copy the * .a / *. So files and the header file into the project.
  6. Download the C library and implementation into the jni folder, in the function of the android class created in step 1 as a system library.

The section below describes how to include the OpenSSL library in an application and call it in the java class.

Create a new project in Eclipse, for example EncryptFileOpenSSL. Either using eclipse (right-click the project name in the Project Explorer), or use the terminal to create the jni folder, and inside it are two subfolders: pre-compiled and include.
Using the terminal:
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

Then add the following line to the jni / Android.mk file:
      …
      LOCAL_MODULE :=    static
      LOCAL_SRC_FILES   :=    pre-compiled/libcrypto.a
      …
      LOCAL_C_INCLUDES  :=    include
      LOCAL_STATIC_LIBRARIES  :=    static –lcrypto
      …

You can then use the functions provided in OpenSSL to implement your encrypt / decrypt / SSL functions. To use Intel AES-NI, use the EVP_ * series function as shown below. In this case, the Intel AES-NI hardware module will be automatically used to encrypt and decrypt AES, if the CPU supports it. For example, when creating a class for encrypting files using the OpenSSL provider, the encryption function in the * .java class will look like this (this source code is taken from Christopher Bird's blog called Sample Code: Application for Data Encryption ).
publiclongencryptFile(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) {
            thrownew IllegalArgumentException("encrypt file execution did not succeed.");
        }
      }
      /* native function available from encodeFile library */public native intencodeFileFromJNI(String fileOut, String fileIn);
    public native voidsetBlocksizeFromJNI(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");
    }

The encryption function in the encodeFile.cpp file that we downloaded using System.loadLibrary will be like this:
intencodeFile(constchar* filenameOut, constchar* 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 uninitializedint 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((unsignedchar *)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" );
      unsignedchar *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 encryptif ( orig_file != NULL ) {
            origData = newunsignedchar[aes_blocksize];
            encData = newunsignedchar[aes_blocksize+EVP_CIPHER_CTX_block_size(e_ctx)]; // potential for encryption to be 16 bytes longer than originalprintf( "Encoding file: %s\n", filename);
            bytesread = fread(origData, 1, aes_blocksize, orig_file);
            // read bytes from file, then send to cipherwhile ( 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 encryptionif (!(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 filesprintf( "\t>>\n");
            fclose(orig_file);
            fclose(enc_file);
      } else {
            printf( "Unable to open files for encoding\n");
            ret = -1;
            return ret;
      }
      return ret;
}

Then use ndk-build to compile to <source of Application>.
/ <path to android-ndk7> / ndk-build APP_ABI = x86
Copy the folder / <PATH \ TO \ OPENSSL> / include / openssl inside the folder </ PATH \ to \ PROJECT \ workspace> / jni / .
* .So / *. A files must be located in / </ PATH \ to \ PROJECT \ workspace> / libs / x86 / or / </ PATH \ to \ PROJECT \ workspace> / libs / armeabi / .
The encode.cpp file used for encryption and decryption should be located in the </ PATH \ to \ PROJECT \ workspace> / jni / folder .

Performance analysis


The following functions allow you to analyze CPU usage, memory usage, and the time taken to encrypt the message. This source code is also taken from Christopher Bird's blog.

CPU usage


The code below helps you read the average CPU load data stored in / proc / stat.
publicfloatreadCPUusage(){
            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();
            }
            return0;
      }

Memory usage


The code snippet below reads the available amount of system memory.
Memory Info is an Android API that provides information about available memory.
So, 1024 bytes = 1 KB, and 1024 KB = 1 MB. Therefore, to convert available memory to megabytes: 1024 * 1024 == 1048576
publiclongreadMem(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);


For more information about compiler optimization, see our optimization notice .

Also popular now: