Click here to Skip to main content
15,867,986 members
Articles / Mobile Apps / Android
Article

Encryption/Decryption - invoking OpenSSL API through JNI calls

12 Feb 2015CPOL4 min read 24.3K   4   1
This blog outlines the steps needed to integrate Intel’s AES-NI instructions into an Android app via the OpenSSL library.

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

Intel® Developer Zone offers tools and how-to information for cross-platform app development, platform and technology information, code samples, and peer expertise to help developers innovate and succeed. Join our communities for Android, Internet of Things, Intel® RealSense™ Technology, and Windows to download tools, access dev kits, share ideas with like-minded developers, and participate in hackathon’s, contests, roadshows, and local events.

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

Java
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-

C++
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

Java
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

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

Timing analysis

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Intel is inside more and more Android devices, and we have tools and resources to make your app development faster and easier.


Comments and Discussions

 
BugCode errors/redundency Pin
Chad3F14-Feb-15 15:44
Chad3F14-Feb-15 15:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.