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:
- Download source code:
wget https://www.openssl.org/source/openssl-1.0.1j.tar.gz - 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
- Create an android project, to encrypt a file in you favorite IDE- the example here is based on Eclipse.
- Declare the OpenSSL related functions as native function through Android.mk file.
- Create a jni folder in the source Android project
- Make a precompiled, include directories under jni.
- Include the openssl library folder created in <OpenSSL source/include/> in the jni folder.
- 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.
- 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.");
}
}
public native int encodeFileFromJNI(String fileOut, String fileIn);
public native void setBlocksizeFromJNI(int blocksize);
public native byte[] generateKeyFromJNI(int keysize);
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];
int seedbytes = 1024;
memset(cKeyBuffer, 0, KEYSIZE );
if (!opensslIsSeeded) {
if (!RAND_load_file("/dev/urandom", seedbytes)) {
return -1;
}
opensslIsSeeded = 1;
}
if (!RAND_bytes((unsigned char *)cKeyBuffer, KEYSIZE )) {
}
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;
if (!(EVP_EncryptInit_ex(e_ctx, EVP_aes_256_cbc(), NULL, cKeyBuffer, iv ))) {
ret = -1;
printf( "ERROR: EVP_ENCRYPTINIT_EX\n");
}
if ( orig_file != NULL ) {
origData = new unsigned char[aes_blocksize];
encData = new unsigned char[aes_blocksize+EVP_CIPHER_CTX_block_size(e_ctx)];
printf( "Encoding file: %s\n", filename);
bytesread = fread(origData, 1, aes_blocksize, orig_file);
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 );
bytesread = fread(origData, 1, aes_blocksize, orig_file);
}
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 );
EVP_CIPHER_CTX_free(e_ctx);
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();
stop = System.currentTimeMillis();
seconds = (stop - start);