出于项目需要,再一次的拿起 OpenSSL,原本以为按照老方法可以方便的移植,但是在 Mac 最新版本 + NDK 最新版本 + iOS SDK 10.3 + OpenSSL 1.1.0e 的环境下,似乎老的移植方法已经不那么好用了。
微店大神区长之前给出了移植的方案(点击文末阅读原文可查看),很可惜的,这个方案并不完美工作,还是有一些工作需要自己来做。
移植到 Android
直接上脚本,其中需要注意的是,克隆出来的 toolchain 我是按架构存放了不同的版本,因为编译出来的产物,还可能被其他的组件使用,比如说 curl 等。
#!/bin/sh
if [ ! -f "openssl-1.1.0e.tar.gz" ]; then
wget https://www.openssl.org/source/openssl-1.1.0e.tar.gz
fi
if [ ! -d "openssl-1.1.0e" ]; then
tar zxf openssl-1.1.0e.tar.gz
fi
# env
if [ -d "out/openssl" ]; then
rm -fr "out/openssl"
fi
mkdir "out"
mkdir "out/openssl"
_compile() {
SURFIX=$1
TOOL=$2
ARCH_FLAGS=$3
ARCH_LINK=$4
CFGNAME=$5
ARCH=$6
if [ ! -d "out/openssl/${SURFIX}" ]; then
mkdir "out/openssl/${SURFIX}"
fi
if [ ! -d "toolchain_${SURFIX}" ]; then
$ANDROID_NDK/build/tools/make-standalone-toolchain.sh --arch=${ARCH} --install-dir=./toolchain_${SURFIX}
fi
export ANDROID_HOME=`pwd`
export TOOLCHAIN=$ANDROID_HOME/toolchain_${SURFIX}
export CROSS_SYSROOT=$TOOLCHAIN/sysroot
export PATH=$TOOLCHAIN/bin:$PATH
export CC=$TOOLCHAIN/bin/${TOOL}-gcc
export CXX=$TOOLCHAIN/bin/${TOOL}-g++
export LINK=${CXX}
export LD=$TOOLCHAIN/bin/${TOOL}-ld
export AR=$TOOLCHAIN/bin/${TOOL}-ar
export RANLIB=$TOOLCHAIN/bin/${TOOL}-ranlib
export STRIP=$TOOLCHAIN/bin/${TOOL}-strip
export ARCH_FLAGS=$ARCH_FLAGS
export ARCH_LINK=$ARCH_LINK
export CFLAGS="${ARCH_FLAGS} -fpic -ffunction-sections -funwind-tables -fstack-protector -fno-strict-aliasing -finline-limit=64"
export CXXFLAGS="${CFLAGS} -frtti -fexceptions"
export LDFLAGS="${ARCH_LINK}"
cd openssl-1.1.0e/
./Configure ${CFGNAME} --prefix=$TOOLCHAIN/sysroot/usr/local --with-zlib-include=$TOOLCHAIN/sysroot/usr/include --with-zlib-lib=$TOOLCHAIN/sysroot/usr/lib zlib no-asm no-shared no-unit-test
make clean
make -j4
make install
cd ..
mv openssl-1.1.0e/libssl.a out/openssl/${SURFIX}/
mv openssl-1.1.0e/libcrypto.a out/openssl/${SURFIX}/
}
# arm
_compile "armeabi" "arm-linux-androideabi" "-mthumb" "" "android" "arm"
# armv7
_compile "armeabi-v7a" "arm-linux-androideabi" "-march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16" "-march=armv7-a -Wl,--fix-cortex-a8" "android-armeabi" "arm"
# x86
_compile "x86" "i686-linux-android" "-march=i686 -msse3 -mstackrealign -mfpmath=sse" "" "android-x86" "x86"
echo "done"
区长还提供了更多架构的编译方案,如下:
# arm64v8
_compile "arm64-v8a" "aarch64-linux-android" "" "" "android64-aarch64" "arm64"
# x86_64
_compile "x86_64" "x86_64-linux-android" "-march=x86-64 -m64 -msse4.2 -mpopcnt -mtune=intel" "" "android64" "x86_64"
# mips
_compile "mips" "mipsel-linux-android" "" "" "android-mips" "mips"
# mips64
_compile "mips64" "mips64el-linux-android" "" "" "linux64-mips64" "mips64"
移植到 iOS
iOS 平台比 Android 要折腾一些,先前我从网上找了很多移植的脚本,都不管用,在最新的环境下均无法编译通过,无奈之下只好自己写。
#!/bin/sh
if [ ! -f "openssl-1.1.0e.tar.gz" ]; then
wget https://www.openssl.org/source/openssl-1.1.0e.tar.gz
fi
if [ ! -d "openssl-1.1.0e" ]; then
tar zxf openssl-1.1.0e.tar.gz
fi
if [ -d "out/openssl" ]; then
rm -fr "out/openssl"
fi
mkdir "out"
mkdir "out/openssl"
mkdir "out/openssl/mac"
mkdir "out/openssl/ios"
SDK_VERSION=10.3
DEVELOPER=`xcode-select -print-path`
_compileMac() {
ARCH=$1
TARGET=$2
cd openssl-1.1.0e
./Configure ${TARGET}
make clean
make -j4
mv libssl.a libssl-${ARCH}.a
mv libcrypto.a libcrypto-${ARCH}.a
cd ..
}
_compileMac "i386" "darwin-i386-cc"
_compileMac "x86_64" "darwin64-x86_64-cc"
lipo -create openssl-1.1.0e/libssl-i386.a openssl-1.1.0e/libssl-x86_64.a -output out/openssl/mac/libssl.a
lipo -create openssl-1.1.0e/libcrypto-i386.a openssl-1.1.0e/libcrypto-x86_64.a -output out/openssl/mac/libcrypto.a
_compileIOS() {
ARCH=$1
PLATFORM=$2
TARGET=$3
cd openssl-1.1.0e
export PLATFORM=$PLATFORM
export CROSS_TOP="${DEVELOPER}/Platforms/${PLATFORM}.platform/Developer"
export CROSS_SDK="${PLATFORM}${SDK_VERSION}.sdk"
export BUILD_TOOLS="${DEVELOPER}"
export CC="${BUILD_TOOLS}/usr/bin/gcc -arch ${ARCH}"
sed -ie "s!static volatile sig_atomic_t intr_signal;!static volatile intr_signal;!" "crypto/ui/ui_openssl.c"
./Configure ${TARGET} no-asm no-unit-test
sed -ie "s!^CFLAG=!CFLAG=-isysroot ${CROSS_TOP}/SDKs/${CROSS_SDK} -miphoneos-version-min=8.0 !" "Makefile"
make clean
make -j4
mv libssl.a libssl-${ARCH}.a
mv libcrypto.a libcrypto-${ARCH}.a
cd ..
}
_compileIOS "arm64" "iPhoneOS" "iphoneos-cross"
_compileIOS "armv7" "iPhoneOS" "iphoneos-cross"
_compileIOS "x86_64" "iPhoneSimulator" "darwin64-x86_64-cc"
_compileIOS "i386" "iPhoneSimulator" "iphoneos-cross"
lipo -create openssl-1.1.0e/libssl-arm64.a openssl-1.1.0e/libssl-armv7.a openssl-1.1.0e/libssl-i386.a openssl-1.1.0e/libssl-x86_64.a -output out/openssl/ios/libssl.a
lipo -create openssl-1.1.0e/libcrypto-arm64.a openssl-1.1.0e/libcrypto-armv7.a openssl-1.1.0e/libcrypto-i386.a openssl-1.1.0e/libcrypto-x86_64.a -output out/openssl/ios/libcrypto.a
echo "done"
这里需要注意是,要设置最低的兼容版本为 8.0,否则在实际使用时,就蛋疼了。这里同时也编译了供 mac 使用的版本。
Linux 编译和服务器端 JNI 程序的使用
要在 Linux 上完成编译,脚本相对简单,直接使用以下代码就好了:
#!/bin/sh
if [ ! -f "openssl-1.1.0e.tar.gz" ]; then
wget https://www.openssl.org/source/openssl-1.1.0e.tar.gz
fi
if [ ! -d "openssl-1.1.0e" ]; then
tar zxf openssl-1.1.0e.tar.gz
fi
cd openssl-1.1.0e/
./config
make clean
make -j4
cd ..
echo "done"
编译后会得到 libssl.a 和 libcrypto.a,这两个库即可用于静态链接,同样的也可以用于 JNI。
准备都做好之后,用一个简单的算法调用来进行验证,下面就以 AES 为例吧:
int aes_encrypt(const char* in, char* key, char* out) {
if(!in || !key || !out) return 0;
AES_KEY aes;
if(AES_set_encrypt_key((unsigned char*)key, 128, &aes) < 0) {
return 0;
}
size_t len = strlen(in);
int en_len = 0;
while(en_len < len) {
AES_encrypt((unsigned char*)in, (unsigned char*)out, &aes);
in += AES_BLOCK_SIZE;
out += AES_BLOCK_SIZE;
en_len += AES_BLOCK_SIZE;
}
return 1;
}
int aes_decrypt(const char* in, char* key, char* out) {
if(!in || !key || !out) return 0;
AES_KEY aes;
if(AES_set_decrypt_key((unsigned char*)key, 128, &aes) < 0) {
return 0;
}
size_t len = strlen(in);
int en_len = 0;
while(en_len < len) {
AES_decrypt((unsigned char*)in, (unsigned char*)out, &aes);
in += AES_BLOCK_SIZE;
out += AES_BLOCK_SIZE;
en_len += AES_BLOCK_SIZE;
}
return 1;
}
char* encode(const char* key, const char* text) {
size_t len = strlen(text) * 8;
char sourceStringTemp[len];
char destStringTemp[len];
memset((char*)sourceStringTemp, 0, len); // fillchar
memset((char*)destStringTemp, 0, len); // fillchar
strcpy((char*)sourceStringTemp, text);
char k[len];
memset((char*)k, 0, len);
strcpy((char*)k, key);
char finalDest[len];
memset((char*)finalDest, 0, len);
char f[3];
if (aes_encrypt(sourceStringTemp, k, destStringTemp)) {
for(int i= 0;destStringTemp[i];i+=1){
memset((char*)f, 0, 3);
sprintf(f, "%0x", (unsigned char)destStringTemp[i]);
strcat(finalDest, f);
printf("%0x ", (unsigned char)destStringTemp[i]);
}
printf("\n");
}
char* ret = (char*)malloc(len);
strcpy(ret, finalDest);
return ret;
}
char* decode(const char* key, const char* text) {
size_t len = strlen(text) * 8;
char sourceStringTemp[len];
char destStringTemp[len];
memset((char*)sourceStringTemp, 0, len); // fillchar
memset((char*)destStringTemp, 0, len); // fillchar
char k[len];
memset((char*)k, 0, len);
strcpy((char*)k, key);
// read encoded string
int step = 0;
int idx = 0;
char f[5];
int nValude = 0;
char tmp[len];
memset((char*)tmp, 0, len);
strcpy((char*)tmp, text);
while (1) {
memset((char*)f, 0, 5);
f[0] = '0';
f[1] = 'x';
f[2] = tmp[idx];
f[3] = tmp[idx + 1];
sscanf(f, "%x", &nValude);
printf("%0x ", nValude);
sourceStringTemp[step] = nValude;
idx += 2;
step++;
if (!tmp[idx]) {
break;
}
}
printf("\n");
aes_decrypt(sourceStringTemp, k, destStringTemp);
char* ret = (char*)malloc(len);
strcpy(ret, destStringTemp);
return ret;
}
首先实现简单的函数,然后就是调用,调用的时候是相当简单的:
const char* text = "abcdefg";
const char* key = "123456";
char* enc = encode(key, text);
char* dec = decode(key, enc);
printf("origin => %s\nenc => %s\ndec => %s\n", text, enc, dec);
free(enc);
free(dec);
要实现 JNI 的话,也是非常简单,服务器端程序是 Java 的话会很容易使用,只需要导出一个头部,并做一些简单的修改即可:
#include <jni.h>
#ifndef _java_exported_
#define _java_exported_
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL Java_com_sample_AES_encode(JNIEnv*, jclass, jstring, jstring);
JNIEXPORT jstring JNICALL Java_com_sample_AES_decode(JNIEnv*, jclass, jstring, jstring);
#ifdef __cplusplus
}
#endif
#endif
再写一个 Java 的引用文件即可:
package com.sample;
public class AES {
static {
try {
System.loadLibrary("sample");
} catch (Throwable th) {
System.out.println("load error => " + th.getMessage());
}
}
public static native String encode(String key, String text);
public static native String decode(String key, String text);
}
最后就是编译,带有 JNI 的情况下,必须手动找 jni.h 的路径,因此编译脚本是这样的:
#!/bin/sh
JNIPATH=/usr/lib/jvm/java-8-openjdk-amd64/include
g++ main.cpp \
-Iopenssl \
-I${JNIPATH} \
-I${JNIPATH}/linux \
-L. -lcrypto \
-shared -fpic \
-o libsample.so
再给一个 Java 端调用的脚本吧,这样就完整了:
#!/bin/sh
JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
JRE_HOME=${JAVA_HOME}/jre
CLASSPATH=.:${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/tools.jar
export JAVA_HOME=${JAVA_HOME}
export JRE_HOME=${JRE_HOME}
export CLASSPATH=${CLASSPATH}
export LD_LIBRARY_PATH=.:${LD_LIBRARY_PATH}
cd com/sample
rm *.class
javac *.java
cd ../../
java com/sample/Main
End
推荐阅读

