猫哥不懂技术

莫催稿,催稿也不交

再谈 OpenSSL 的跨平台

出于项目需要,再一次的拿起 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


发表评论:

Powered By Z-BlogPHP 1.5.1 Zero

Copyright Rarnu 2017. All Rights Reserved.