В этом блоге перечисляются действия по интеграции инструкций Intel AES-NI в приложение Android с помощью библиотеки OpenSSL. Выполнив приведенную здесь инструкцию, вы сможете создать приложение JNI, использующее ускорение AES-NI.
Новые инструкции шифрования стандарта AES (Intel AES-NI)
Инструкции Intel AES-NI были предложены в марте 2008 г. в качестве расширения набора инструкций архитектуры х86 для микропроцессоров Intel. Цель этого набора инструкций состоит в повышении производительности, безопасности и энергоэффективности приложений, выполняющих шифрование и расшифровку данных по стандарту AES.
Использование Intel AES-NI в Android
Алгоритмы AES в составе библиотеки OpenSSL продемонстрировали существенно более высокую производительность по сравнению с нативными алгоритмами Java. Причина в том, что эта библиотека оптимизирована для процессоров Intel и использует инструкции AES-NI. Ниже приводится пошаговое описание шифрования файла с помощью провайдера OpenSSL.
Начиная с Android 4.3, в OpenSSL в AOSP присутствует поддержка Intel AES-NI, поэтому вам достаточно скомпилировать код с нужной конфигурацией. Также можно загрузить его с официального веб-сайта и скомпилировать самостоятельно, а затем использовать файл *.a/*.so напрямую в вашем проекте. Получить библиотеки шифрования можно двумя способами.
Если у вас нет исходного кода AOSP, можно загрузить OpenSSL здесь. Используйте последнюю версию, чтобы избежать всех известных уязвимостей, обнаруженных в прежних версиях OpenSSL. AOSP включает интегрированную библиотеку openssl, которую можно поместить в папку jni приложения для доступа к входящим в ее состав папкам.
Если вы загружаете исходный код openssl для самостоятельной компиляции и создания библиотеки, используйте следующее.
1. Загрузите исходный код:
wget https://www.openssl.org/source/openssl-1.0.1j.tar.gz.
2. Компилируйте: выполните следующую команду в консоли (обратите внимание, что нужно задать для переменной NDK полный путь к вашему дистрибутиву):
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
После этого файл libcrypto.a появится в папке верхнего уровня. Для использования файла *.so введите Configure shared android-x86 ***.
При наличии исходного кода AOSP цепочка инструментов ndk не нужна.
source build/envsetiup.sh
lunch <options>
make –j8
cd external/openssl
mm
При этом libcrypto.a компилируется и помещается в каталог out/host/linux_x86/bin.
Используйте OpenSSL через NDK в проекте Android
Создайте проект Android для шифрования файлов в вашей любимой среде разработки. Здесь рассматривается пример с Eclipse.
- Объявите функции, связанные с OpenSSL, как native function в файле Android.mk.
- Создайте папку jni в исходном проекте Android.
- Создайте заранее скомпилированные папки include внутри папки jni.
- Включите папку библиотеки OpenSSL, созданную в <OpenSSL source/include/>, в папку jni.
- Затем реализуйте шифрование, написав функцию C в jni/*.c. После этого нужно скопировать файлы *.a/*.so и файл заголовка в проект.
- Загрузите библиотеку и реализацию на C в папку jni, в функции класса android, созданного на шаге 1 в виде системной библиотеки.
В приведенном ниже разделе описывается, как включить библиотеку OpenSSL в приложение и вызвать ее в классе java.
Создайте в Eclipse новый проект, например EncryptFileOpenSSL. Либо с помощью eclipse (щелкните правой кнопкой мыши имя проекта в обозревателе проектов), либо с помощью терминала создайте папку jni, а внутри нее — две вложенные папки: pre-compiled и include.
С помощью терминала:
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
Затем добавьте следующую строку в файл jni/Android.mk:
…
LOCAL_MODULE := static
LOCAL_SRC_FILES := pre-compiled/libcrypto.a
…
LOCAL_C_INCLUDES := include
LOCAL_STATIC_LIBRARIES := static –lcrypto
…
Затем можно использовать функции, предоставленные в OpenSSL, для реализации ваших функций encrypt/decrypt/SSL. Чтобы использовать Intel AES-NI, используйте функцию серии EVP_*, как показано ниже. При этом аппаратный модуль Intel AES-NI будет автоматически задействован для шифрования и расшифровки AES, если ЦП это поддерживает. Например, при создании класса для шифрования файлов с помощью провайдера OpenSSL функция шифрования в классе *.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");
}
Функция шифрования в файле encodeFile.cpp, который мы загрузили с помощью System.loadLibrary, будет такой:
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: %sn" ,filename );
printf ("enc filename: %sn" ,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_EXn");
}
// 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: %sn", 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_ENCRYPTUPDATEn");
}
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_EXn");
}
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 encodingn");
ret = -1;
return ret;
}
return ret;
}
Затем используем ndk-build для компиляции в <source of Application>.
/<path to android-ndk7>/ndk-build APP_ABI=x86
Скопируйте папку /<PATHTOOPENSSL>/include/openssl внутрь папки </PATHtoPROJECTworkspace>/jni/.
Файлы *.so/*.a должны находиться в /</PATHtoPROJECTworkspace>/libs/x86/ или /</PATHtoPROJECTworkspace>/libs/armeabi/.
Файл encode.cpp, используемый для шифрования и расшифровки, должен находиться в папке </PATHtoPROJECTworkspace>/jni/.
Анализ производительности
Следующие функции позволяют проанализировать использование ЦП, использование памяти и время, затраченное на шифрование сообщения. Этот исходный код также взят из блога Кристофера Берда.
Использование ЦП
Приведенный ниже код помогает прочесть данные о средней нагрузке на ЦП, хранящиеся в /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 Info — это API Android, позволяющий получать информацию о доступной памяти.
Итак,1024 байта = 1 КБ, а 1024 КБ = 1 МБ. Поэтому, чтобы преобразовать доступную память в мегабайты: 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);
Дополнительные сведения об оптимизации компиляторов см. в нашем уведомлении об оптимизации.
Автор: saul