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


Дополнительные сведения об оптимизации компиляторов см. в нашем уведомлении об оптимизации.

Комментарии (1)


  1. selenite
    22.04.2015 14:10

    Вот только эталонная имплементация SSL в Android — сейчас, все же, BoringSSL. (их вообще куча — LibreSSL, MatrixSSL и иже с ними..)