В этом блоге перечисляются действия по интеграции инструкций 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: %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; }
Затем используем ndk-build для компиляции в <source of Application>.
/<path to android-ndk7>/ndk-build APP_ABI=x86
Скопируйте папку /<PATH\TO\OPENSSL>/include/openssl внутрь папки </PATH\to\PROJECT\workspace>/jni/.
Файлы *.so/*.a должны находиться в /</PATH\to\PROJECT\workspace>/libs/x86/ или /</PATH\to\PROJECT\workspace>/libs/armeabi/.
Файл encode.cpp, используемый для шифрования и расшифровки, должен находиться в папке </PATH\to\PROJECT\workspace>/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);
Дополнительные сведения об оптимизации компиляторов см. в нашем уведомлении об оптимизации.
ссылка на оригинал статьи http://habrahabr.ru/post/255525/
Добавить комментарий