Шифрование и расшифровка — обращение к API OpenSSL с помощью вызовов JNI

от автора

В этом блоге перечисляются действия по интеграции инструкций 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.

  1. Объявите функции, связанные с OpenSSL, как native function в файле Android.mk.
  2. Создайте папку jni в исходном проекте Android.
  3. Создайте заранее скомпилированные папки include внутри папки jni.
  4. Включите папку библиотеки OpenSSL, созданную в <OpenSSL source/include/>, в папку jni.
  5. Затем реализуйте шифрование, написав функцию C в jni/*.c. После этого нужно скопировать файлы *.a/*.so и файл заголовка в проект.
  6. Загрузите библиотеку и реализацию на 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *