赞
踩
OpenSSL 源代码:https://www.openssl.org/source/ ,这里使用 3.0.12
LTS 版本(1.1.1
等低版本已经被停止支持并被官方强烈推荐停止使用)。
本文档及源码已经放置到 Github:https://github.com/tangramor/OpenSSL-Android-iOS
我们会验证一个用 OpenSSL 签名的文件。首先我们用 OpenSSL 命令行工具来构建一对 私钥/公钥,并生成一个自签名的证书,再使用私钥对一个文件签名并生成签名文件。
如果是生产环境,私钥一定要保管到安全稳妥的地方,不能散布出去。
# 生成私钥
openssl genpkey -algorithm RSA -out private_key.pem
# 生成公钥
openssl pkey -in private_key.pem -out public_key.pem -pubout
# 生成证书申请
openssl req -new -key private_key.pem -out cert.csr
# 生成自签名证书
openssl x509 -req -days 1024 -in cert.csr -signkey private_key.pem -out certificate.crt
# 对文件进行签名
openssl dgst -sha256 -sign private_key.pem -out signature.bin <要签名的文件>
# 验证文件签名
openssl dgst -sha256 -verify public_key.pem -signature signature.bin <要验证签名的文件>
这里假定我们对 MyFile.txt
文件进行了签名,生成了 signature.bin
签名文件。
后面的 Android 和 iOS/Mac 验证签名项目里用到的有(这里没有用公钥来验签,从证书里可以获取公钥):
MyFile.txt
signature.bin
certificate.crt
以下操作我是在 Windows 10 下 WSL2 的 Ubuntu 环境里完成的。
build.sh
脚本:
#!/bin/bash -e WORK_PATH=/mnt/d/workspace/openssl-3.0.12 #$(cd "$(dirname "$0")";pwd) ANDROID_NDK_PATH=/home/tattoo/workspace/android-ndk-r21e OPENSSL_SOURCES_PATH=${WORK_PATH} ANDROID_TARGET_API=21 ANDROID_TARGET_ABI=$1 OUTPUT_PATH=${WORK_PATH}/openssl_3.0.12_${ANDROID_TARGET_ABI} OPENSSL_TMP_FOLDER=/tmp/openssl_${ANDROID_TARGET_ABI} mkdir -p ${OPENSSL_TMP_FOLDER} cp -r ${OPENSSL_SOURCES_PATH}/* ${OPENSSL_TMP_FOLDER} function build_library { mkdir -p ${OUTPUT_PATH} make && make install rm -rf ${OPENSSL_TMP_FOLDER} rm -rf ${OUTPUT_PATH}/bin rm -rf ${OUTPUT_PATH}/share rm -rf ${OUTPUT_PATH}/ssl rm -rf ${OUTPUT_PATH}/lib/engines* rm -rf ${OUTPUT_PATH}/lib/pkgconfig rm -rf ${OUTPUT_PATH}/lib/ossl-modules echo "Build completed! Check output libraries in ${OUTPUT_PATH}" } if [ "$ANDROID_TARGET_ABI" == "armeabi-v7a" ] then export ANDROID_NDK_ROOT=${ANDROID_NDK_PATH} PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$ANDROID_NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin:$ANDROID_NDK_ROOT/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin:$PATH cd ${OPENSSL_TMP_FOLDER} ./Configure android-arm -D__ANDROID_API__=${ANDROID_TARGET_API} -static no-asm no-shared no-tests --prefix=${OUTPUT_PATH} build_library elif [ "$ANDROID_TARGET_ABI" == "arm64-v8a" ] then export ANDROID_NDK_ROOT=${ANDROID_NDK_PATH} PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$ANDROID_NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin:$ANDROID_NDK_ROOT/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin:$PATH cd ${OPENSSL_TMP_FOLDER} ./Configure android-arm64 -D__ANDROID_API__=${ANDROID_TARGET_API} -static no-asm no-shared no-tests --prefix=${OUTPUT_PATH} build_library elif [ "$ANDROID_TARGET_ABI" == "x86" ] then export ANDROID_NDK_ROOT=${ANDROID_NDK_PATH} PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$ANDROID_NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin:$ANDROID_NDK_ROOT/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin:$PATH cd ${OPENSSL_TMP_FOLDER} ./Configure android-x86 -D__ANDROID_API__=${ANDROID_TARGET_API} -static no-asm no-shared no-tests --prefix=${OUTPUT_PATH} build_library elif [ "$ANDROID_TARGET_ABI" == "x86_64" ] then export ANDROID_NDK_ROOT=${ANDROID_NDK_PATH} PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin:$ANDROID_NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin:$ANDROID_NDK_ROOT/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin:$PATH cd ${OPENSSL_TMP_FOLDER} ./Configure android-x86_64 -D__ANDROID_API__=${ANDROID_TARGET_API} -static no-asm no-shared no-tests --prefix=${OUTPUT_PATH} build_library else echo "Unsupported target ABI: $ANDROID_TARGET_ABI" exit 1 fi
将上面的脚本放置到 OpenSSL 源码目录,修改目录路径(WORK_PATH
为源码路径,ANDROID_NDK_PATH
为 NDK 安装路径)后,执行下面的语句来编译出 4 个架构的静态库:
./build.sh armeabi-v7a
./build.sh arm64-v8a
./build.sh x86
./build.sh x86_64
输出的库文件、头文件目录在源码目录下的 openssl_3.0.12_arm64-v8a
、openssl_3.0.12_armeabi-v7a
、openssl_3.0.12_x86
、openssl_3.0.12_x86_64
,其中 include
目录下的头文件完全一致,只需要拷贝一份即可。
使用官包在 Android Studio 里编译时可能出现如下错误:
cmd.exe /C "cd . && C:\Users\tattoo\AppData\Local\Android\Sdk\ndk\25.1.8937393\toolchains\llvm\prebuilt\windows-x86_64\bin\clang++.exe --target=x86_64-none-linux-android24 --sysroot=C:/Users/tattoo/AppData/Local/Android/Sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/windows-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -fno-limit-debug-info -static-libstdc++ -Wl,--build-id=sha1 -Wl,--no-rosegment -Wl,--fatal-warnings -Wl,--gc-sections -Wl,--no-undefined -Qunused-arguments -shared -Wl,-soname,libsignverify.so -o D:\workspace\signverify\app\build\intermediates\cxx\Debug\n6l545p2\obj\x86_64\libsignverify.so CMakeFiles/signverify.dir/signverify/Verifier.cpp.o CMakeFiles/signverify.dir/native-lib.cpp.o D:/workspace/signverify/app/src/main/cpp/signverify/lib/x86_64/libssl.a D:/workspace/signverify/app/src/main/cpp/signverify/lib/x86_64/libcrypto.a -landroid -llog -latomic -lm && cd ."
ld: error: relocation R_X86_64_PC32 cannot be used against symbol 'bio_type_lock'; recompile with -fPIC
>>> defined in D:/workspace/signverify/app/src/main/cpp/signverify/lib/x86_64/libcrypto.a(libcrypto-lib-bio_meth.o)
>>> referenced by bio_meth.c
>>> libcrypto-lib-bio_meth.o:(BIO_get_new_index) in archive D:/workspace/signverify/app/src/main/cpp/signverify/lib/x86_64/libcrypto.a
这是因为在前面编译 OpenSSL 库时,需要每一个 .o 文件都使用 -fPIC
选项。这里修改了 OpenSSL 源码目录下 Configurations\00-base-templates.conf
的内容,加上了 -fPIC
:
# -*- Mode: perl -*- my %targets=( DEFAULTS => { template => 1, cflags => "-fPIC", cppflags => "-fPIC", lflags => "-fPIC", defines => [], includes => [], lib_cflags => "-fPIC", lib_cppflags => "-fPIC", lib_defines => [], thread_scheme => "(unknown)", # Assume we don't know thread_defines => [], unistd => "<unistd.h>", shared_target => "-fPIC", shared_cflag => "-fPIC", shared_defines => [], shared_ldflag => "-fPIC", shared_rcflag => "",
打开 Android Studio,创建一个新的 Native C++ 项目:
语言选择 Java
,Build configuration language 选择 Groovy DSL
:
其它选择缺省值,这样一个新的项目就创建成功了。
切换到 Project 视图,在 app -> src -> main 上单击鼠标右键,创建一个新目录 assets
:
然后把前面我们生成的证书、签名和测试文件都拷贝到 assets
目录下:
在 app -> src -> main -> cpp 上单击鼠标右键,创建一个新目录 openssl
,然后把前面编译的 OpenSSL 静态库和头文件目录都拷贝到里面:
在 openssl
目录下创建两个文件 Verifier.cpp
和 Verifier.h
。
Verifier.h
:
#ifndef TESTOPENSSL_VERIFIER_H #define TESTOPENSSL_VERIFIER_H #include <iostream> #include <fstream> #include <sstream> #include <vector> #include <algorithm> #include <iterator> #include <cstring> #include <numeric> #include <openssl/rsa.h> #include <openssl/pem.h> #include <openssl/evp.h> #include <openssl/x509.h> #include <openssl/bio.h> #include <openssl/err.h> #include <android/log.h> #include <android/asset_manager_jni.h> #include <android/asset_manager.h> using namespace std; bool verifyFile(AAssetManager *pAsm); X509 *loadCertificate(const string &certEntityString); string getKeyStrFromPublickKey(EVP_PKEY *publicKey); bool verifySignature(const string &data, const string &publicKeyStr, const string &signatureStr); #endif //TESTOPENSSL_VERIFIER_H
Verifier.cpp
:
#include "Verifier.h" bool verifyFile(AAssetManager *pAsm) { AAsset *assetSign = AAssetManager_open(pAsm, "signature.bin", AASSET_MODE_UNKNOWN); AAsset *assetCert = AAssetManager_open(pAsm, "certificate.crt", AASSET_MODE_UNKNOWN); AAsset *assetFile = AAssetManager_open(pAsm, "MyFile.txt", AASSET_MODE_UNKNOWN); if (NULL == assetSign || NULL == assetCert || NULL == assetFile) { __android_log_print(ANDROID_LOG_INFO, __FUNCTION__, "failed to read file, signature or certificate assets"); return false; } // 获取文件内容 off_t bufferSignSize = AAsset_getLength(assetSign); char *bufferSign = (char *) malloc(bufferSignSize + 1); bufferSign[bufferSignSize] = 0; int numSignRead = AAsset_read(assetSign, bufferSign, bufferSignSize); off_t bufferCertSize = AAsset_getLength(assetCert); char *bufferCert = (char *) malloc(bufferCertSize + 1); bufferCert[bufferCertSize] = 0; int numCertRead = AAsset_read(assetCert, bufferCert, bufferCertSize); off_t bufferFileSize = AAsset_getLength(assetFile); char *bufferFile = (char *) malloc(bufferFileSize + 1); bufferFile[bufferFileSize] = 0; int numFileRead = AAsset_read(assetFile, bufferFile, bufferFileSize); X509 *certificate = loadCertificate(bufferCert); // Create a signature object EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); const EVP_MD *md = EVP_sha256(); EVP_MD_CTX_init(mdctx); EVP_VerifyInit_ex(mdctx, md, NULL); // Initialize the signature verification, extract the public key from the certificate EVP_PKEY *publicKey = X509_get_pubkey(certificate); if (publicKey == nullptr) { std::cout << "Cannot get public key" << std::endl; return 1; } // PEM_write_PUBKEY(stdout, publicKey); //print public key string publicKeyStr = getKeyStrFromPublickKey(publicKey); bool verified = verifySignature(bufferFile, publicKeyStr, bufferSign); return verified; } string getKeyStrFromPublickKey(EVP_PKEY *publicKey) { // 创建一个BIO以将公钥内容输出到字符串 string publicKeyStr; BIO *publicKeyBio = BIO_new(BIO_s_mem()); if (PEM_write_bio_PUBKEY(publicKeyBio, publicKey) == 1) { // 将BIO中的数据读取到字符串变量中 char *buffer; long publicKeySize = BIO_get_mem_data(publicKeyBio, &buffer); if (publicKeySize > 0) { publicKeyStr.assign(buffer, publicKeySize); } } // std::cout << "Publick key: \n" << publicKeyStr << std::endl; return publicKeyStr; } // Verify the sinature with File content, Public Key and Signature strings bool verifySignature(const string &data, const string &publicKeyStr, const string &signatureStr) { ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); // 读取公钥字符串到 BIO BIO *publicKeyBIO = BIO_new_mem_buf(publicKeyStr.c_str(), -1); RSA *rsa = nullptr; // 从 BIO 中解析 PEM 格式的公钥 rsa = PEM_read_bio_RSA_PUBKEY(publicKeyBIO, &rsa, nullptr, nullptr); // 创建 EVP_MD_CTX 对象用于验证 EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); EVP_MD_CTX_init(md_ctx); // 设置要验证的哈希算法 (SHA256 in this case) const EVP_MD *md = EVP_sha256(); // 在 EVP_MD_CTX 对象中设置公钥 EVP_PKEY *pkey = EVP_PKEY_new(); EVP_PKEY_assign_RSA(pkey, rsa); EVP_MD_CTX_set_pkey_ctx(md_ctx, EVP_PKEY_CTX_new(pkey, nullptr)); // 更新签名算法的上下文,使用要验证的数据(例如文件内容) EVP_DigestVerifyInit(md_ctx, nullptr, md, nullptr, pkey); // 更新签名算法的上下文,使用要验证的数据(例如文件内容) EVP_DigestUpdate(md_ctx, data.c_str(), data.size()); // 验证签名 int result = EVP_DigestVerifyFinal(md_ctx, reinterpret_cast<const unsigned char *>(signatureStr.c_str()), signatureStr.size()); // 释放资源 EVP_MD_CTX_free(md_ctx); EVP_cleanup(); CRYPTO_cleanup_all_ex_data(); ERR_free_strings(); // 根据验证结果输出校验结果 if (result == 1) { std::cout << "文件验证成功" << std::endl; } else { std::cout << "文件验证失败" << std::endl; } // 返回验证结果 return (result == 1); } // Load Certificate content and return X509 object X509 *loadCertificate(const string &certEntityString) { BIO *bio = BIO_new(BIO_s_mem()); string certStr = "-----BEGIN CERTIFICATE-----\n" + certEntityString + "\n-----END CERTIFICATE-----\n"; const char *certificate = certStr.c_str(); // std::cout << "Cert:\n" << certificate << std::endl; BIO_puts(bio, certificate); X509 *cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); if (cert == nullptr) { std::cout << "Cannot read certificate" << std::endl; } BIO_free(bio); return cert; }
修改 cpp 项目目录下的 native-lib.cpp
:
#include <jni.h>
#include <string>
#include "openssl/Verifier.h"
extern "C" JNIEXPORT jstring JNICALL
Java_com_openssltest_sdk_testopenssl_MainActivity_stringFromJNI(
JNIEnv* env,
jobject thiz,
jobject assetManager) {
AAssetManager *pAsm = AAssetManager_fromJava(env, assetManager);
return verifyFile(pAsm) ? env->NewStringUTF("File Verified Successfully") : env->NewStringUTF("File Verify failed");
}
修改 java 项目目录下的 MainActivity.java
:
import android.content.res.AssetManager;
;tv.setText(stringFromJNI());
改为 tv.setText(stringFromJNI(this.getAssets()));
修改 cpp 项目目录下的 CMakeLists.txt
(添加了 openssl 的头文件和静态库,以及我们新加的源代码):
cmake_minimum_required(VERSION 3.22.1) project("testopenssl") # OpenSSL 头文件 include_directories(${CMAKE_CURRENT_SOURCE_DIR}/openssl/include) # OpenSSL 静态库 add_library(local_crypto STATIC IMPORTED) add_library(local_openssl STATIC IMPORTED) set_target_properties(local_crypto PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/openssl/lib/${ANDROID_ABI}/libcrypto.a) set_target_properties(local_openssl PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/openssl/lib/${ANDROID_ABI}/libssl.a) add_library(${CMAKE_PROJECT_NAME} SHARED # List C/C++ source files with relative paths to this CMakeLists.txt. # 新加的源码文件 openssl/Verifier.cpp native-lib.cpp) target_link_libraries(${CMAKE_PROJECT_NAME} # List libraries link to the target library # 链接 OpenSSL 静态库 local_openssl local_crypto android log)
修改 app 目录下的 build.gradle
文件,因为当前 Android Studio 版本的原因,需要调整 compileSdk
到 34:
plugins { id 'com.android.application' } android { namespace 'com.openssltest.sdk.testopenssl' compileSdk 34 defaultConfig { applicationId "com.openssltest.sdk.testopenssl" minSdk 24 targetSdk 34 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { checkReleaseBuilds false } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } externalNativeBuild { cmake { path file('src/main/cpp/CMakeLists.txt') version '3.22.1' } } buildFeatures { viewBinding true } } dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.10.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' }
编译运行,如果没有问题,应该在模拟器里显示如下结果:
尝试修改一下 MyFile.txt
文件内容,再编译运行,应该输出 File Verify failed
。
我们可以运行一遍 Gradle 的构建任务 build
或 buildNeeded
,Android Studio 会帮我们生成不同平台下可用的 libtestopenssl.so
库文件 (在app/build/intermediates/stripped_native_libs/<release or debug>/out/lib/
目录下),我们可以在其它 Android 应用里使用它们了:
以下操作在 Macbook Pro(M1芯片,MacOS Sonoma 14.0)上完成。
需要确认一下本地开发环境,比如执行 xcode-select -print-path
,看看路径是否正确。我这里需要运行 sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer
来设定正确的开发环境变量。
以下只针对 iOS 真机、iOS 模拟器 和 MacOS 环境进行编译,其它环境可以类似操作。
在编译的时候我碰到了一些编译问题,例如 .../clang/15.0.0/include/inttypes.h:21:15: fatal error: 'inttypes.h' file not found
这种离奇的报错。这时候不要慌,重启一下系统或许就解决了……
OpenSSL iOS 编译指南:https://wiki.openssl.org/index.php/Compilation_and_Installation#iOS
cd ~/workspace/openssl-3.0.12
export CC=clang
export CROSS_TOP=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer
export CROSS_SDK=iPhoneOS.sdk
export PATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:$PATH
./Configure ios64-cross no-shared no-dso no-hw no-engine --prefix="/Users/tattoo/workspace/openssl-3.0.12/openssl-ios64"
make
make install
make clean
这里 CROSS_SDK
的值可以 ls /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs
来查看。一般是一个到具体 SDK 目录的软连接。
具体的平台选项(这里我们用了 ios64-cross
)可以查看源码目录下的 Configurations/15-ios.conf
。
为了与后面 MacOS 的库进行区分,这里把编译出来的静态库改名为:
libssl-iOS.a
libcrypto-iOS.a
模拟器跟真机的库是有区别的,需要单独编译。
cd ~/workspace/openssl-3.0.12
export CC=clang
export CROSS_TOP=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer
export CROSS_SDK=iPhoneSimulator.sdk
export PATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:$PATH
./Configure iossimulator-xcrun no-shared no-dso no-hw no-engine --prefix="/Users/tattoo/workspace/openssl-3.0.12/openssl-iossimulator"
make
make install
make clean
为了与后面 MacOS 的库进行区分,这里把编译出来的静态库改名为:
libssl-iossimulator.a
libcrypto-iossimulator.a
OpenSSL MacOS 编译指南:https://wiki.openssl.org/index.php/Compilation_and_Installation#OS_X
cd ~/workspace/openssl-3.0.12 # 编译 x86_64 版本 ./Configure darwin64-x86_64-cc --prefix="/Users/tattoo/workspace/openssl-3.0.12/openssl-darwin-x86_64" no-asm make make install make clean # 编译 arm64 版本 ./Configure darwin64-arm64-cc --prefix="/Users/tattoo/workspace/openssl-3.0.12/openssl-darwin-arm64" no-asm make make install make clean # 合并静态库 mkdir -p /Users/tattoo/workspace/openssl-3.0.12/openssl-darwin/ lipo -create /Users/tattoo/workspace/openssl-3.0.12/openssl-darwin-x86_64/lib/libssl.a /Users/tattoo/workspace/openssl-3.0.12/openssl-darwin-arm64/lib/libssl.a -output /Users/tattoo/workspace/openssl-3.0.12/openssl-darwin/libssl.a lipo -create /Users/tattoo/workspace/openssl-3.0.12/openssl-darwin-x86_64/lib/libcrypto.a /Users/tattoo/workspace/openssl-3.0.12/openssl-darwin-arm64/lib/libcrypto.a -output /Users/tattoo/workspace/openssl-3.0.12/openssl-darwin/libcrypto.a
具体的平台选项可以查看源码目录下的 Configurations/10-main.conf
。
其生成的 include
目录下的头文件完全一致,只需要拷贝一份即可。
这里我们用一个测试的 C++ 项目来同样验证前面 OpenSSL 签名的文件(见 构建密钥、证书及测试签名 )。
在 XCode 里创建一个命令行项目,代码类型选择 C++:
使用 Xcode 在项目里加入一个新的 C++ 文件 Verifier.cpp
,Xcode 会贴心的创建对应的 Verifier.hpp
头文件。
Verifier.hpp
:
#ifndef Verifier_hpp #define Verifier_hpp #include <iostream> #include <fstream> #include <sstream> #include <vector> #include <algorithm> #include <iterator> #include <cstring> #include <numeric> #include <openssl/rsa.h> #include <openssl/pem.h> #include <openssl/evp.h> #include <openssl/x509.h> #include <openssl/bio.h> #include <openssl/err.h> using namespace std; class Verifier { public: Verifier(){}; virtual ~Verifier(){}; virtual bool verifyFile(); virtual X509 *loadCertificate(const string &certEntityString); virtual string getKeyStrFromPublickKey(EVP_PKEY *publicKey); virtual bool verifySignature(const string &data, const string &publicKeyStr, const string &signatureStr); virtual string readFromFile(const string &filename); }; #endif /* Verifier_hpp */
Verifier.cpp
:
#include "Verifier.hpp" bool Verifier::verifyFile() { // Load public key certificate and signature file string signStr = readFromFile("signature.bin"); string certStr = readFromFile("certificate.crt"); X509 *certificate = loadCertificate(certStr); // Read the file to verify (assuming it is a text file) string fileData = readFromFile("MyFile.txt"); // Create a signature object EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); const EVP_MD *md = EVP_sha256(); EVP_MD_CTX_init(mdctx); EVP_VerifyInit_ex(mdctx, md, NULL); // Initialize the signature verification, extract the public key from the certificate EVP_PKEY *publicKey = X509_get_pubkey(certificate); if (publicKey == nullptr) { std::cout << "Cannot get public key" << std::endl; return 1; } // PEM_write_PUBKEY(stdout, publicKey); //print public key string publicKeyStr = getKeyStrFromPublickKey(publicKey); bool verified = verifySignature(fileData, publicKeyStr, signStr); return verified; } string Verifier::getKeyStrFromPublickKey(EVP_PKEY *publicKey) { // 创建一个BIO以将公钥内容输出到字符串 string publicKeyStr; BIO *publicKeyBio = BIO_new(BIO_s_mem()); if (PEM_write_bio_PUBKEY(publicKeyBio, publicKey) == 1) { // 将BIO中的数据读取到字符串变量中 char *buffer; long publicKeySize = BIO_get_mem_data(publicKeyBio, &buffer); if (publicKeySize > 0) { publicKeyStr.assign(buffer, publicKeySize); } } // std::cout << "Publick key: \n" << publicKeyStr << std::endl; return publicKeyStr; } // Verify the sinature with File content, Public Key and Signature strings bool Verifier::verifySignature(const string &data, const string &publicKeyStr, const string &signatureStr) { ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); // 读取公钥字符串到 BIO BIO *publicKeyBIO = BIO_new_mem_buf(publicKeyStr.c_str(), -1); RSA *rsa = nullptr; // 从 BIO 中解析 PEM 格式的公钥 rsa = PEM_read_bio_RSA_PUBKEY(publicKeyBIO, &rsa, nullptr, nullptr); // 创建 EVP_MD_CTX 对象用于验证 EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); EVP_MD_CTX_init(md_ctx); // 设置要验证的哈希算法 (SHA256 in this case) const EVP_MD *md = EVP_sha256(); // 在 EVP_MD_CTX 对象中设置公钥 EVP_PKEY *pkey = EVP_PKEY_new(); EVP_PKEY_assign_RSA(pkey, rsa); EVP_MD_CTX_set_pkey_ctx(md_ctx, EVP_PKEY_CTX_new(pkey, nullptr)); // 更新签名算法的上下文,使用要验证的数据(例如文件内容) EVP_DigestVerifyInit(md_ctx, nullptr, md, nullptr, pkey); // 更新签名算法的上下文,使用要验证的数据(例如文件内容) EVP_DigestUpdate(md_ctx, data.c_str(), data.size()); // 验证签名 int result = EVP_DigestVerifyFinal(md_ctx, reinterpret_cast<const unsigned char *>(signatureStr.c_str()), signatureStr.size()); // 释放资源 EVP_MD_CTX_free(md_ctx); EVP_cleanup(); CRYPTO_cleanup_all_ex_data(); ERR_free_strings(); // 根据验证结果输出校验结果 if (result == 1) { std::cout << "文件验证成功" << std::endl; } else { std::cout << "文件验证失败" << std::endl; } // 返回验证结果 return (result == 1); } // Load Certificate content and return X509 object X509* Verifier::loadCertificate(const string &certEntityString) { BIO *bio = BIO_new(BIO_s_mem()); const char *certificate = certEntityString.c_str(); // std::cout << "Cert:\n" << certificate << std::endl; BIO_puts(bio, certificate); X509 *cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); if (cert == nullptr) { std::cout << "Cannot read certificate" << std::endl; } BIO_free(bio); return cert; } // Read a text file and return string string Verifier::readFromFile(const string &filename) { std::ifstream file(filename); if (!file) { // Return an empty string if unable to open the file return ""; } string content((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>())); file.close(); // std::cout << "File data: \n" << content << std::endl; return content; }
代码里会报错,因为我们还没有把前面的 OpenSSL 库引入。
在当前 main.cpp
所在目录下创建一个 openssl
目录,将前面编译的 libcrypto-iOS.a
、libssl-iOS.a
、libcrypto-iossimulator.a
、libssl-iossimulator.a
以及 ~/workspace/openssl-3.0.12/openssl-darwin/libcrypto.a
和 ~/workspace/openssl-3.0.12/openssl-darwin/libssl.a
拷贝到 openssl
目录,然后把前面 ~/workspace/openssl-3.0.12/openssl-darwin-arm64/include
整个目录也拷贝到 openssl
目录。
在 Xcode 的项目上用鼠标右键弹出菜单,选择 “Add Files to…” ,把刚才的 openssl
目录加入到项目里。
再把静态库加入到项目的 Framework and Libraries 里:
把头文件搜索路径添加到 Header Search Paths 里(值为 ${SRCROOT}/<项目名>/openssl/include
):
修改项目的 Schema,让项目工作路径为当前源码目录 ${SRCROOT}/TestOpenssl
把前面 OpenSSL 命令行生成的证书、签名和测试文件也放置到代码相同的目录下:
修改 main.cpp
:
#include "Verifier.hpp"
int main(int argc, const char * argv[]) {
Verifier *verifier = new Verifier();
return verifier->verifyFile() ? 0 : 1;
}
命令行编译:
clang++ -g Verifier.cpp main.cpp -o main -I /Users/tattoo/workspace/TestOpenssl/TestOpenssl/openssl/include -lssl -lcrypto -L /Users/tattoo/workspace/TestOpenssl/TestOpenssl/openssl -std=c++11
运行测试:
./main
文件验证成功
# 修改 MyFile.txt 内容后再运行
./main
文件验证失败
编译静态库:
clang++ -g -c Verifier.cpp -o verifier.o -I /Users/tattoo/workspace/TestOpenssl/TestOpenssl/openssl/include -lssl -lcrypto -L /Users/tattoo/workspace/TestOpenssl/TestOpenssl/openssl -std=c++11 -lobjc -framework CoreFoundation
libtool -static -o libverifier.a *.o openssl/libssl.a openssl/libcrypto.a
在这个项目里,我们想把 Verifier.cpp
直接编译成支持 iOS 真机 和 iOS 模拟器 的静态库,这样就不用把源代码到处拷贝了:
# iOS 真机 # 查看 SDK 路径 xcrun -sdk iphoneos --show-sdk-path clang++ -g -c Verifier.cpp -o verifier-iOS.o -I /Users/tattoo/workspace/TestOpenssl/TestOpenssl/openssl/include -lssl -lcrypto -L /Users/tattoo/workspace/TestOpenssl/TestOpenssl/openssl -std=c++11 -lobjc -framework CoreFoundation -arch arm64 -mios-version-min=7.0.0 -fno-common -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk libtool -static -o libverifier-iOS.a *-iOS.o openssl/*-iOS.a # iOS 模拟器 # 查看 SDK 路径 xcrun -sdk iphonesimulator --show-sdk-path clang++ -g -c Verifier.cpp -o verifier-iossimulator.o -I /Users/tattoo/workspace/TestOpenssl/TestOpenssl/openssl/include -lssl -lcrypto -L /Users/tattoo/workspace/TestOpenssl/TestOpenssl/openssl -std=c++11 -lobjc -framework CoreFoundation -DIOS_PLATFORM=SIMULATOR64 -fno-common -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator17.0.sdk libtool -static -o libverifier-iossimulator.a *-iossimulator.o openssl/*-iossimulator.a
Xcode 里新建一个项目,选择 iOS App:
Interface 选择 Storyboard
,Language 选择 Objective-C
:
选中左侧 Main.Storyboard
,给 View 里添加一个 Text View,调整一下大小、字体啥的。
把前面项目里用到和生成的文件加入到这个新项目:
⚠️ 截图里的 Verifier.cpp
文件并不需要……openssl
目录下的静态库也都删掉只保留 include
目录内容。
因为我们的库是 C++ 的,所以把引用头文件的 ViewController.m
文件名称改为 ViewController.mm
。
选中左侧 Main.Storyboard
,点击右上角的添加编辑器图标,这样 Xcode 就分屏成左右两边了。左边选择 ViewController.mm
,右边还是 Main.Storyboard
,然后点击前面创建的 Text View,按住 Ctrl 键,拖动鼠标到左侧编辑器合适位置,这样一个 Property IBOutlet 就自动创建了,命名为 Text
:
修改 ViewController.mm
代码如下:
#import "ViewController.h" #import "Verifier.hpp" @interface ViewController () @property (weak, nonatomic) IBOutlet UITextView *Text; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. Verifier verifier = Verifier(); bool verified = verifier.verifyFile(); _Text.text = verified ? @"File Verified Successfully" : @"File Verify Failed"; } @end
编译运行项目:
注意:如果是使用模拟器,那么静态库需要使用前面构建的 libverifier-iossimulator.a
,真机则需要改为 libverifier-iOS.a
。
不使用此方法的原因是虽然编译出来了静态库,但因为此项目脚本魔改了头文件,而修改后头文件的宏在 Xcode 中可能会有无法预知的问题,比如我就遇到了 openssl/bn.h:186:39 Unknown type name 'BN_ULONG'
的报错……
克隆 https://github.com/x2on/OpenSSL-for-iPhone 到本地
然后进入 OpenSSL-for-iPhone 目录,运行 ./build-libssl.sh
脚本即可编译。目前缺省版本为 openssl-1.1.1w 。
编译完成后的静态库放置在当前目录的 lib 子目录,头文件在 include 子目录。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。