暗号化と復号化-JNI呼び出しを使用してOpenSSL APIにアクセスする

このブログでは、OpenSSLライブラリを使用してIntel AES-NIの手順をAndroidアプリケーションに統合する手順を示しています。 ここの指示に従うことにより、AES-NIアクセラレーションを使用するJNIアプリケーションを作成できます。



新しいAES暗号化命令(Intel AES-NI)



Intel AES-NI命令は、Intelマイクロプロセッサ用のx86アーキテクチャ命令セットの拡張として2008年3月に導入されました。 この一連の命令の目的は、AES標準を使用してデータを暗号化および復号化するアプリケーションのパフォーマンス、セキュリティ、およびエネルギー効率を向上させることです。



AndroidでIntel AES-NIを使用する



OpenSSLライブラリのAESアルゴリズムは、ネイティブJavaアルゴリズムと比較して大幅に高いパフォーマンスを示しました。 その理由は、このライブラリがIntelプロセッサ向けに最適化されており、AES-NI命令を使用しているためです。 以下は、OpenSSLプロバイダーを使用したファイル暗号化の段階的な説明です。



Android 4.3以降、AOSPのOpenSSLはIntel AES-NIをサポートしているため、必要な構成でコードをコンパイルするだけで済みます。 公式Webサイトからダウンロードして自分でコンパイルし、* .a / *。を使用してプロジェクトで直接ファイルを作成することもできます。 暗号化ライブラリを取得するには2つの方法があります。



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-aexport 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ディレクトリに配置されます。

AndroidプロジェクトでNDK経由でOpenSSLを使用する

お気に入りの開発環境でファイルを暗号化するAndroidプロジェクトを作成します。 Eclipseの例を次に示します。

  1. Android.mkファイルでOpenSSL関連の関数をネイティブ関数として宣言します。
  2. 元のAndroidプロジェクトにjniフォルダーを作成します。
  3. jniフォルダー内にプリコンパイル済みインクルードフォルダーを作成します。
  4. jniフォルダの<OpenSSL source / include />で作成されたOpenSSLライブラリフォルダを含めます。
  5. 次に、jni / *。CでC関数を記述して暗号化を実装します。 その後、* .a / *。のファイルとヘッダーファイルをプロジェクトにコピーする必要があります。
  6. システムライブラリとして手順1で作成したAndroidクラスの機能で、Cライブラリと実装をjniフォルダにダウンロードします。


以下のセクションでは、アプリケーションにOpenSSLライブラリを組み込み、javaクラスで呼び出す方法について説明します。



EncryptFileOpenSSLなど、Eclipseで新しいプロジェクトを作成します。 Eclipseを使用するか(プロジェクトエクスプローラーでプロジェクト名を右クリック)、またはターミナルを使用してjniフォルダーを作成し、その中に2つのサブフォルダー(プリコンパイル済みとインクルード)があります。

ターミナルの使用:

  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で提供される機能を使用して、暗号化/復号化/ SSL機能を実装できます。 Intel AES-NIを使用するには、以下に示すようにEVP_ *シリーズ関数を使用します。 この場合、CPUがサポートしている場合、Intel AES-NIハードウェアモジュールがAESの暗号化と復号化に自動的に使用されます。 たとえば、OpenSSLプロバイダーを使用してファイルを暗号化するためのクラスを作成する場合、* .javaクラスの暗号化関数は次のようになります(このソースコードは、 サンプルコード:データ暗号化のアプリケーションと呼ばれるChristopher Birdのブログから引用 )。

 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"); }
      
      





System.loadLibraryを使用してダウンロードしたencodeFile.cppファイルの暗号化関数は次のようになります。

 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>にコンパイルします。

/ <android-ndk7へのパス> / ndk-build APP_ABI = x86

フォルダをコピーします/ <PATH \ TO \ OPENSSL> / include / opensslフォルダ内</ PATH \ to \ PROJECT \ workspace> / jni /

* .So / *。ファイルは、 / </ PATH \ to \ PROJECT \ workspace> / libs / x86 /または/ </ PATH \ to \ PROJECT \ workspace> / libs / armeabi /に配置する必要があります。

暗号化および復号化に使用されるencode.cppファイルは、 </ PATH \ to \ PROJECT \ workspace> / jni / folderに配置する必要があります



パフォーマンス分析



次の関数を使用すると、CPU使用率、メモリ使用率、およびメッセージの暗号化にかかった時間を分析できます。 このソースコードは、Christopher Birdのブログからも引用しています。



CPU使用率



以下のコードは、/ proc / statに保存されている平均CPU負荷データを読み取るのに役立ちます。

 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; }
      
      





メモリ使用量



以下のコードスニペットは、システムメモリの利用可能な量を読み取ります。

メモリ情報は、利用可能なメモリに関する情報を提供するAndroid APIです。

したがって、1024バイト= 1 KB、および1024 KB = 1 MBです。 したがって、使用可能なメモリをメガバイトに変換するには: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);
      
      







コンパイラの最適化の詳細については、 最適化に関する注意事項を参照してください。



All Articles