Encryption/Decryption - invoking OpenSSL API through JNI calls

This blog outlines the steps needed to integrate Intel’s AES-NI instructions into an Android app via the OpenSSL library. By following the procedures here, you’ll be able to build a JNI application that benefits from AES-NI acceleration.

Intel Advanced Encryption Standard New Instructions (Intel AES-NI)

Intel AES-NI was proposed in March, 2008 and is an extension of the x86 instruction set architecture for Intel microprocessors. The purpose of the instruction set is to improve the performance, security, and power efficiency of applications performing encryption and decryption using the Advanced Encryption Standard (AES).

Using Intel AES-NI on Android

The OpenSSL library’s AES algorithms show significant performance gains over those provided by the native Java Provider. This is because the library is optimized for Intel processors and makes use of the AES-NI instructions. Below is a step-by-step description of how to encrypt a file using OpenSSL provider.

Beginning with Android 4.3, OpenSSL in Android Open Source Project (AOSP) supports Intel AES-NI, so you just need to compile it with the correct configuration. Also, you can download it from the official website and compile it yourself, then use the *.a/*.so in your project directly. There are two ways to get the cryptographic libraries.

If you do not own a AOSP source, then you can download OpenSSL from  http://www.openssl.org/source/. The usage of latest version enables us to prevent any known vulnerabilities against older versions of openssl. The AOSP comes with an integrated openssl library which can be directly put in the applications jni folder to access the included directories.

If you are downloading the openssl source to cross compile and create the library yourself implement the following:

  1. Download source code:

    wget https://www.openssl.org/source/openssl-1.0.1j.tar.gz
     
  2. Compile ‒ Run the following command on your console (note that you should set the NDK variable to the full path of your distribution):
        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

Then you can get libcrypto.a in the top directory. If you want to use *.so file, enter “Configure shared android-x86 ***”.

If you have an AOSP source code, you don’t need the ndk tool chain,

      source build/envsetiup.sh

      lunch <options>

      make –j8

      cd external/openssl

      mm

This builds libcrypto.a and places it in out/host/linux_x86/bin

Use OpenSSL via the NDK in Android Project

  1. Create an android project, to encrypt a file in you favorite IDE- the example here is based on Eclipse.
  2. Declare the OpenSSL related functions as native function through Android.mk file.
  3. Create a jni folder in the source Android project
  4. Make a precompiled, include directories under jni.
  5.  Include the openssl library folder created in <OpenSSL source/include/> in the jni folder.
  6. Then implement encryption by writing a C function to do so  in the jni/*.c. After doing that, you need to copy the *.a/*.so and header file into the project.
  7. Load the library and c implementation in jni folder,  in the android class function created in step 1 as a system library.

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

Create a new project, for example EncryptFileOpenSSL in Eclipse. Either using eclipse (Right click on Project name on the Project Explorer or using a terminal create a directory jni, and then two sub directories- pre-compiled & include.

Using a 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 into the jni/Android.mk file:

      …

      LOCAL_MODULE :=    static

      LOCAL_SRC_FILES   :=    pre-compiled/libcrypto.a

      …

      LOCAL_C_INCLUDES  :=    include

      LOCAL_STATIC_LIBRARIES  :=    static –lcrypto

      …

Then, you can use functions provided by OpenSSL to implement your encrypt/decrypt/SSL functions. To use Intel AES-NI, just use the EVP_* series function as shown below, which will automatically use Intel AES-NI to accelerate AES encryption/decryption if the CPU supports it. For example if you writing a class to encrypt a file,  using OpenSSL provider, the function for encryption in  *.java class would look like this (this source code is taken from Christopher Bird’s blog titled, “Sample Code: Data Encryption Application”)

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");
    }

Now, the encryption function in  encodeFile.cpp, that we loaded using the System.loadLibrary would be-

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;
}

 

Then use ndk-build to compile in the <source of Application>.

       /<path to android-ndk>/ndk-build APP_ABI=x86

copy /<PATH\TO\OPENSSL>/include/openssl directory inside </PATH\to\PROJECT\workspace>/jni/.

The *.so/*.a  should be place in /</PATH\to\PROJECT\workspace>/libs/x86/. Or /</PATH\to\PROJECT\workspace>/libs/armeabi/.

The encode.cpp file thatis used for encryption/decryption should be placed in </PATH\to\PROJECT\workspace>/jni/.

Performance analysis

Following functions let us analyze the cpu usage, memory used and time taken to encrypt a file. Again, this source code is taken from Christopher Bird’s blog.

CPU Usage:

The code below helps one to read the average CPU usage using the information stored in /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 Usage:

The below code segment reads the available system memory.

Memory Info is an Android API that enables us to retrieve information regarding available memory.

Now,1024 Bytes = 1 kB & 1024 kB    = 1 MB. Therefore, to convert the available memory into MB- 1024*1024 == 1048576

      public long readMem(ActivityManager am) {
            MemoryInfo mi = new MemoryInfo();
            am.getMemoryInfo(mi);
            long availableMegs = mi.availMem / 1048576L;
            return availableMegs;
      }

Timing analysis:

start = System.currentTimeMillis();
// Perform Encryption.
stop = System.currentTimeMillis();
seconds = (stop - start);

 

For more complete information about compiler optimizations, see our Optimization Notice.