📜 ⬆️ ⬇️

Encryption and decryption - accessing the OpenSSL API using JNI calls

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 using AES-NI acceleration.

New encryption instructions standard AES (Intel AES-NI)


Intel AES-NI instructions were proposed in March 2008 as an extension of the x86 architecture instruction set for Intel microprocessors. The purpose of this instruction set is to improve 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 as part of the OpenSSL library have demonstrated significantly better performance compared to the native Java algorithms. The reason is that this library is optimized for Intel processors and uses AES-NI instructions. Below is a step-by-step description of file encryption using the OpenSSL provider.

Beginning with Android 4.3, OpenSSL has Intel AES-NI support in AOSP, 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. You can get encryption libraries in two ways.
')
If you do not have the AOSP source code, you can download OpenSSL here . Use the latest version to avoid all known vulnerabilities found in previous versions of OpenSSL. AOSP includes the openssl integrated library, which can be placed in the jni folder of the application to access its member folders.
If you download openssl source code for self-compilation and library creation, 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 NDK variable to the full path to 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 

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

At the same time, libcrypto.a is compiled and placed in the out / host / linux_x86 / bin directory.
Use OpenSSL via NDK in the Android project
Create an Android project to encrypt files in your favorite development environment. This is an example of Eclipse.
  1. Declare functions related to OpenSSL as the native function in the Android.mk file.
  2. Create a 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 to jni / *. C. After that, you need to copy the * .a / *. So files and the header file into the project.
  6. Download the library and the implementation in C to the jni folder, as a 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 browser), or use the terminal to create the jni folder and inside it two subfolders: pre-compiled and include.
Using 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 feature as shown below. In this case, the hardware module Intel AES-NI 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 Byrd's blog called 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"); } 

The encryption function in the encodeFile.cpp file that we downloaded using System.loadLibrary will be this:
 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 <source of Application>.
/ <path to android-ndk7> / ndk-build APP_ABI = x86
Copy the / <PATH \ TO \ OPENSSL> / include / openssl folder inside the </ PATH \ to \ PROJECT \ workspace> / jni / folder .
The * .so / *. A files must be 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 features allow you to analyze CPU usage, memory usage, and time taken to encrypt a message. This source code is also taken from Christopher Byrd's blog.

CPU usage


The code below helps to read the average CPU load data 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 following code snippet reads the available system memory.
Memory Info is an Android API that allows you to get information about available memory.
So, 1024 bytes = 1 KB, and 1024 KB = 1 MB. Therefore, to convert available memory to megabytes: 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); 


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

Source: https://habr.com/ru/post/255525/


All Articles