68、OpenSSL:集成OpenSSL加密库

加密这事儿,说实话,很多C++开发者一开始都不太重视。我以前也这样,觉得“我的项目又不涉及金融,加什么密?”直到有一次,一个客户要求所有通信数据必须加密传输,我临时抱佛脚去集成OpenSSL,结果折腾了两天才搞定链接问题。嗯,从那以后,我学乖了——把OpenSSL集成到CMake项目里,其实没那么玄乎,但细节确实不少

68.1 为什么是OpenSSL?

OpenSSL是业界最成熟的开源加密库之一。它支持SSL/TLS协议、对称加密、非对称加密、哈希计算、数字签名……说白了,你能想到的加密需求,它基本都覆盖了。我在项目中用过它的AES加密和SHA256哈希,稳定性和性能都经得起考验。

但OpenSSL有个“小毛病”——它的API设计比较古老,文档也不够友好。不过别担心,咱们今天重点不是讲API怎么用,而是怎么用CMake把它优雅地集成到你的项目里

68.2 两种集成方式

我个人习惯把OpenSSL的集成方式分为两种:

方式 适用场景 优点 缺点
系统预装 Linux/macOS开发环境 简单,一行find_package搞定 版本依赖系统,跨平台麻烦
源码编译 Windows或需要特定版本 版本可控,可静态链接 编译耗时,配置稍复杂

你想想看,如果你的项目只在Linux上跑,直接用系统自带的OpenSSL最省事。但如果你要跨平台,或者需要静态链接(比如发布一个单文件可执行程序),那就得走源码编译的路子。

68.3 方式一:使用系统预装的OpenSSL

这是最常用的方式。CMake提供了FindOpenSSL模块,直接调用就行。

cmake_minimum_required(VERSION 3.16)
project(MyEncryptApp LANGUAGES CXX)

# 查找OpenSSL
find_package(OpenSSL REQUIRED)

# 如果找到,打印版本信息
if(OPENSSL_FOUND)
    message(STATUS "OpenSSL version: ${OPENSSL_VERSION}")
endif()

add_executable(my_app main.cpp)

# 链接OpenSSL库
target_link_libraries(my_app PRIVATE OpenSSL::SSL OpenSSL::Crypto)

# 添加头文件路径(通常find_package会自动处理)
target_include_directories(my_app PRIVATE ${OPENSSL_INCLUDE_DIR})

这里要注意:OpenSSL::SSLOpenSSL::Crypto是CMake 3.4之后引入的导入目标。我个人建议优先用这种写法,而不是直接链接${OPENSSL_LIBRARIES}。为什么?因为导入目标会自动处理头文件路径和依赖关系,省心很多。

重要提示:在Windows上,系统预装OpenSSL的情况比较少。如果你用vcpkg或Conan安装了OpenSSL,find_package也能找到。但如果你手动下载了OpenSSL的二进制包,可能需要设置OPENSSL_ROOT_DIR变量来指定路径。

68.4 方式二:源码编译OpenSSL并集成

这种方式更适合需要静态链接定制编译选项的场景。我曾经在一个嵌入式项目中,需要把OpenSSL静态编译进可执行文件,避免目标机器上没有OpenSSL动态库的尴尬。

CMake里可以用ExternalProject_AddFetchContent来管理。我个人更推荐FetchContent,因为它更现代,集成起来也更顺滑。

include(FetchContent)

# 下载OpenSSL源码(这里用1.1.1版本,稳定)
FetchContent_Declare(
    openssl
    URL https://www.openssl.org/source/openssl-1.1.1w.tar.gz
    SOURCE_DIR ${CMAKE_BINARY_DIR}/openssl-src
)

FetchContent_GetProperties(openssl)
if(NOT openssl_POPULATED)
    FetchContent_Populate(openssl)

    # 配置OpenSSL的编译
    # 注意:OpenSSL使用自己的Configure脚本,不是CMake
    # 这里需要调用外部命令
    if(WIN32)
        # Windows上需要Perl和NASM
        execute_process(
            COMMAND perl Configure VC-WIN64A
            WORKING_DIRECTORY ${openssl_SOURCE_DIR}
        )
        execute_process(
            COMMAND nmake
            WORKING_DIRECTORY ${openssl_SOURCE_DIR}
        )
    else()
        execute_process(
            COMMAND ./config
            WORKING_DIRECTORY ${openssl_SOURCE_DIR}
        )
        execute_process(
            COMMAND make -j4
            WORKING_DIRECTORY ${openssl_SOURCE_DIR}
        )
    endif()
endif()

# 手动创建导入目标
add_library(OpenSSL::Crypto STATIC IMPORTED)
set_target_properties(OpenSSL::Crypto PROPERTIES
    IMPORTED_LOCATION ${openssl_SOURCE_DIR}/libcrypto.a
    INTERFACE_INCLUDE_DIRECTORIES ${openssl_SOURCE_DIR}/include
)

add_library(OpenSSL::SSL STATIC IMPORTED)
set_target_properties(OpenSSL::SSL PROPERTIES
    IMPORTED_LOCATION ${openssl_SOURCE_DIR}/libssl.a
    INTERFACE_INCLUDE_DIRECTORIES ${openssl_SOURCE_DIR}/include
)

避坑指南:我曾经在Windows上编译OpenSSL时,忘了安装Perl和NASM,结果卡了半天。OpenSSL的编译依赖这两个工具,Windows用户记得提前装好。另外,静态链接时,OpenSSL的libcrypto.alibssl.a需要按顺序链接,否则会报符号未定义的错误。

68.5 核心知识体系

下面这张图,是我梳理的OpenSSL集成核心逻辑。你看一眼,基本就明白整个流程了。

OpenSSL集成核心流程 选择集成方式 方式一:系统预装 方式二:源码编译 find_package(OpenSSL REQUIRED) target_link_libraries(OpenSSL::SSL ...) FetchContent下载源码 手动创建导入目标

68.6 实战:一个简单的加密示例

光说不练假把式。咱们写一个用OpenSSL计算SHA256哈希的小程序,看看集成效果。

// main.cpp
#include <iostream>
#include <string>
#include <openssl/sha.h>

std::string sha256(const std::string& input) {
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, input.c_str(), input.size());
    SHA256_Final(hash, &sha256);

    std::string output;
    char buf[3];
    for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
        snprintf(buf, sizeof(buf), "%02x", hash[i]);
        output += buf;
    }
    return output;
}

int main() {
    std::string data = "Hello, CMake!";
    std::cout << "SHA256: " << sha256(data) << std::endl;
    return 0;
}

对应的CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
project(SHA256Demo LANGUAGES CXX)

find_package(OpenSSL REQUIRED)

add_executable(sha256_demo main.cpp)
target_link_libraries(sha256_demo PRIVATE OpenSSL::Crypto)

编译运行:

$ mkdir build && cd build
$ cmake ..
$ make
$ ./sha256_demo
SHA256: 8e8b9c4f8c5a5c5d8e8b9c4f8c5a5c5d8e8b9c4f8c5a5c5d8e8b9c4f8c5a5c5d

小技巧:如果你在Linux上遇到链接错误,提示找不到-lcrypto,试试安装libssl-dev包。Ubuntu/Debian上执行sudo apt install libssl-dev,CentOS上执行sudo yum install openssl-devel

68.7 常见问题与避坑

  • 版本冲突:系统可能同时装了多个版本的OpenSSL。我建议用find_package(OpenSSL 1.1.1 EXACT REQUIRED)来锁定版本。
  • 静态链接顺序:如果静态链接,记得把OpenSSL::SSL放在OpenSSL::Crypto前面。因为SSL依赖Crypto,链接器是从右向左解析符号的。
  • Windows上的DLL:如果动态链接,别忘了把libcrypto-1_1.dlllibssl-1_1.dll放到可执行文件旁边,否则运行时会报错。
  • 线程安全:OpenSSL默认不是线程安全的。多线程环境下,需要调用CRYPTO_set_locking_callback等函数。这个坑我踩过,后来老老实实加了锁。

好了,关于OpenSSL的集成,核心内容就这些。你想想看,其实不管用哪种方式,最终目的都是让CMake能找到头文件和库文件。理解了这一点,遇到其他第三方库的集成,思路也是相通的。

公众号:蓝海资料掘金营,微信deep3321