加密/解密 - 通过 JNI 调用 OpenSSL API





0/5 (0投票)
本博客概述了通过 OpenSSL 库将 Intel 的 AES-NI 指令集成到 Android 应用所需的步骤。
Intel® 开发者中心提供用于跨平台应用开发、平台和技术信息、代码示例以及同行专业知识的工具和操作指南,以帮助开发者进行创新并取得成功。加入我们的社区,了解 Android、物联网、Intel® RealSense™ 技术和 Windows,下载工具、访问开发套件、与志同道合的开发者交流想法,并参与黑客马拉松、竞赛、路演和本地活动。
本博客概述了通过 OpenSSL 库将 Intel 的 AES-NI 指令集成到 Android 应用所需的步骤。通过遵循此处的步骤,您将能够构建一个受益于 AES-NI 加速的 JNI 应用程序。
Intel 高级加密标准新指令 (Intel AES-NI)
Intel AES-NI 于 2008 年 3 月提出,是 Intel 微处理器 x86 指令集架构的扩展。该指令集旨在提高执行高级加密标准 (AES) 加密和解密的应用程序的性能、安全性和电源效率。
在 Android 上使用 Intel AES-NI
OpenSSL 库的 AES 算法相比原生 Java Provider 提供的算法在性能上有显著提升。这是因为该库针对 Intel 处理器进行了优化,并利用了 AES-NI 指令。下面将分步介绍如何使用 OpenSSL 提供程序加密文件。
从 Android 4.3 开始,Android 开源项目 (AOSP) 中的 OpenSSL 支持 Intel AES-NI,因此您只需使用正确的配置进行编译即可。此外,您也可以从官方网站下载并自行编译,然后直接在项目中使用 **\*.a/\*.so** 文件。获取加密库有两种方式。
如果您没有 AOSP 源代码,则可以从 http://www.openssl.org/source/ 下载 OpenSSL。使用最新版本可以防止出现针对旧版本 openssl 的已知漏洞。AOSP 带有一个集成的 openssl 库,可以直接放入应用程序的 jni 文件夹以访问包含的目录。
如果您下载 openssl 源代码进行交叉编译并自行创建库,请执行以下操作:
- 下载源代码
wget https://www.openssl.org/source/openssl-1.0.1j.tar.gz - 编译 - 在控制台运行以下命令(请注意,您需要将 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 中
通过 NDK 在 Android 项目中使用 OpenSSL
- 创建一个 **Android 项目**,以便在您喜欢的 IDE 中加密文件 - 此处的示例基于 Eclipse。
- 通过 Android.mk 文件将 OpenSSL 相关函数声明为 **native function**。
- 在源 Android 项目中创建一个 jni 文件夹
- 在 jni 下创建一个预编译、包含目录。
- 将创建的 openssl 库文件夹包含在 jni 文件夹的 <OpenSSL source/include/> 中。
- 然后,在 **jni/\*.c** 中编写一个 C 函数来实现加密。之后,您需要将 **\*.a/\*.so** 和头文件复制到项目中。
- 在 jni 文件夹中加载库和 C 实现,在第 1 步创建的 Android 类函数中作为系统库。
下面的部分将介绍如何将 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_** 系列函数,如下所示,如果 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");
}
现在,我们在 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** 在 <Application source> 中进行编译。
/<path to android-ndk>/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/。
性能分析
以下函数使我们能够分析 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;
}
内存使用
以下代码段读取可用系统内存。
Memory Info 是一个 Android API,它使我们能够检索有关可用内存的信息。
现在,1024 字节 = 1 kB & 1024 kB = 1 MB。因此,要将可用内存转换为 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);