OpenSSL 1.1.1c Linux/Windows 编译安装与隔离
简介:面向在 Windows 平台使用 Visual Studio 2015 开发 C/C++ 项目的读者,这套 OpenSSL 1.1.1c 预编译文件可省去从源码下载、环境配置到编译排错的繁琐过程,适合需要快速集成加密通信、证书处理、摘要算法或 TLS 功能的工程,也可用于教学演示、加密算法验证与旧版本兼容性测试。压缩包共 928 个文件,约 43.73MB,以 848 个 h 头文件为主体,并含 16 个 lib 静态库与导入库、16 个 dll 动态库、28 个 pdb 调试符号及少量 c、pl、exe 辅助文件,覆盖 32 位和 64 位下的静态库、动态库版本,目录中可直接取用 libcrypto、libssl 相关库与头文件。已有 1636 人学习下载。对于需要排查链接错误、核对导出符号或调试底层调用的开发者,pdb 与配套文件能提供直接线索;静态库便于生成独立程序,动态库则利于减小体积并统一运行时依赖,可较快完成工程配置与功能验证。
1. 版本卡死在一台老构建机上
接手过一台构建机,工程里的CMakeLists.txt写死了find_package(OpenSSL 1.1.1 REQUIRED),而机器上装的是 3.x 分支。头文件里几个初始化接口位置变了,编到链接阶段直接爆出一屏未定义符号。想改业务代码,翻了一下调用点,牵一发动全身。最后只能退回去,把 OpenSSL_1_1_1c 单独编一份,装进独立前缀,让这个工程自己找自己的库。这种事在离线内网、跨平台打包、老项目维护里特别常见——装本身不难,难的是装完之后系统里躺着两份 OpenSSL,谁先被链接器看见全凭运气,越救越乱。下面这套流程适合两类人:一类是被人塞了一个只认 1.1.1c 的老工程,另一类是要自己在 Linux 和 Windows 上产出这份编译文件、打成包里发给别人。顺序是先定版本和环境,再编 Linux,再编 Windows,最后处理翻车和验证。
2. 环境清单:OpenSSL_1_1_1c 到底吃什么依赖
2.1 版本对表:1.1.1c 在分支里的位置
OpenSSL_1_1_1c 这个 tag 属于 1.1.1 分支,2019 年 5 月发布,是 1.1.1 系列早期的一个 LTS 补丁版本。它和 1.1.0 的 API 基本兼容,和 3.0 之后差别就大了:3.0 把一批老接口标成废弃,构建系统也换成了 Perl 驱动的Configure加Configurations/目录那一套。所以老工程只要头文件里出现 1.1.1 时代的东西,用系统自带的 3.x 编,报错几乎是必然的。
我一般会先把这几个前提确认掉,再动手:
| 项目 | 最低要求 | 我实际用的 | 说明 |
|---|---|---|---|
| C 编译器 | gcc 4.8 | gcc 9.4 | 低于 4.8 会在部分内联汇编上报错 |
| make | 3.81 | make 4.3 | 老 make 对并行目标处理不佳 |
| Perl | 5.10 | 5.30 | Configure本身就是 Perl 脚本 |
| NASM(仅 Windows) | 2.13 | 2.15 | 关掉汇编可用no-asm绕开 |
| zlib(可选) | 1.2.x | 1.2.11 | 要zlib编译选项才需要 |
版本别贪新。有人图省事直接拉 master 编,编出来符号名对不上,排查半天才发现根本不是同一个东西。tag 就锁OpenSSL_1_1_1c,源码包解出来目录名通常是openssl-OpenSSL_1_1_1c。
2.2 Linux 侧的依赖清点
Linux 上编译本身只要编译器和 Perl,但要把产物用在别的工程里,头文件和 pkg-config 文件得齐。装之前先确认这几样在不在:
# 检查基础工具链,缺哪个补哪个 gcc --version make --version perl -v | head -n 2 # Debian 系补依赖 sudo apt-get install -y build-essential perl zlib1g-dev # RHEL 系补依赖 sudo yum install -y gcc gcc-c++ make perl perl-core zlib-devel这段没太多技术含量,但顺序有讲究:先确认perl在 PATH 里,因为./config第一步就是调 Perl 解析平台信息;zlib-devel只有在你打算给zlib参数时才装,不加这个参数编出来的库不带压缩能力,后面接 HTTP 相关功能会少一块。perl-core在部分 RHEL 系发行版上是单独包的,只装perl会出现Can't locate locale.pm这类报错,属于典型的环境不全。
还有一个容易被忽略的点:如果机器上已经有系统自带的 OpenSSL 开发包(libssl-dev/openssl-devel),不要卸。卸了会让系统里一堆依赖它的包跟着坏。正确做法是编译到独立前缀,靠编译参数和运行时路径去隔离,这一点第 3 章会展开。
2.3 Windows 侧的依赖清点
Windows 上编 1.1.1c 是另一套东西:Perl 必须有,NASM 建议有,编译器用 VS 的命令行工具链。三样缺一样都会在perl Configure或nmake阶段卡住。
- Perl:装 Windows 版 Perl 发行版,5.x 任意版本都行,装完把
perl.exe所在目录加到 PATH,命令行敲perl -v能看到版本号才算数。 - NASM:1.1.1 的 x86_64 汇编优化依赖它。不想装也能编,
perl Configure后面加no-asm,代价是性能掉一截,功能不受影响。 - VS 工具链:需要 C++ 桌面开发组件。装完之后从开始菜单进 x64 原生工具命令提示符,或者用
%VS2017INSTALLDIR%\VC\Auxiliary\Build\vcvars64.bat手动初始化环境,后者在写脚本做自动化构建时更好用。
目录我习惯这样分:源码放D:\build\openssl-OpenSSL_1_1_1c,安装前缀放D:\sdk\openssl111c。源码和产物分开,重编的时候直接删安装目录,不用碰源码树。
3. Linux 编译 OpenSSL_1_1_1c:config 参数怎么给
3.1 解包之后先看 config 和 Configure 的区别
源码包解出来,根目录下有两个脚本:config和Configure。前者是个包装脚本,会自动探测当前平台,猜一个 target 传给后者;后者才是真正干活的,要求你显式指定 target,比如linux-x86_64、linux-aarch64。
我的习惯是:x86_64 的普通服务器上直接用./config,省事;交叉编译或者目标平台比较特殊(比如 ARM 服务器、LoongArch)时用./Configure <target>,因为自动探测偶尔会猜错,编出来的汇编路径不对,跑起来直接段错误。
# 解包并进入源码目录 tar -xzf openssl-OpenSSL_1_1_1c.tar.gz cd openssl-OpenSSL_1_1_1c # 看一眼自动探测到的平台,确认没错再往下走 ./config -h | head -n 5-h只是打印帮助,不产生任何构建文件,属于动手前的确认动作。这一步花十秒,能省掉后面半小时的排查。
3.2 ./config 的参数组合:shared、zlib、--prefix
参数是这套流程里最值得花时间的地方。我给一组我常用的组合,然后逐个说清楚:
./config \ --prefix=/usr/local/openssl111c \ --openssldir=/usr/local/openssl111c/ssl \ shared zlib -fPIC--prefix:安装根目录。所有头文件、库、可执行文件都落在这里,和系统自带的/usr完全隔离。这是整篇里最重要的一行,前缀选错,后面全是连锁反应。--openssldir:配置文件和证书的存放目录,默认会跟着 prefix 走,但显式写出来更清楚。openssl version -d查的就是这个路径。shared:编动态库,产出libssl.so.1.1和libcrypto.so.1.1。如果你的程序要动态加载、或者多个进程共用,选这个。反过来,做单文件分发、不想带 .so 的,用no-shared编静态库。zlib:启用 zlib 压缩支持。不加也行,加了之后 TLS 压缩能力可用,但要注意部分安全规范里不推荐开压缩,取舍看你自己的场景。-fPIC:位置无关代码。编动态库时默认会加,但如果你后面还要把这个库链进别的 .so,显式写上更保险。
两个参数不要同时给:shared和no-shared互斥,都写会以后一个为准,看着没报错,产物却和预期不一致,这种坑最难查。
3.3 make、make test 与 make install
配置完就是编译。1.1.1c 的源码量不小,单核编要几分钟,用-j开并行:
# 并行编译,核数按机器实际情况给 make -j$(nproc) # 跑一遍自带测试,确认编出来的东西功能正常 make test # 安装到前面指定的前缀 sudo make installmake test这一步很多人跳过,我不建议。它会跑一遍算法自测和 TLS 握手测试,能提前暴露汇编路径错、zlib 链接失败这类问题。跳过的话,问题会推迟到你自己的业务程序跑起来才炸,那时候排查成本翻好几倍。测试里有个别用例在容器环境下可能因为熵源不足而超时,看到test_rand之类的失败先别慌,补个haveged或者换个有/dev/urandom的环境再跑一遍。
make install装完之后,前缀目录下应该长出这几个东西:bin/openssl、include/openssl/、lib/libssl.so.1.1、lib/libcrypto.so.1.1、lib/pkgconfig/。少哪个都说明安装没走完。
3.4 让链接器和运行器都找得到它
装完不等于能用。这一步是新手最容易漏的:库躺在/usr/local/openssl111c/lib里,系统根本不知道。
# 把新库目录注册给动态链接器 echo "/usr/local/openssl111c/lib" | sudo tee /etc/ld.so.conf.d/openssl111c.conf # 刷新缓存,确认新路径已被收录 sudo ldconfig ldconfig -p | grep openssl111cld.so.conf.d/下每个文件是一行路径,ldconfig读完之后重建/etc/ld.so.cache。这个动作是全局的,加进去之后系统里所有程序在运行时都可能优先找到这份 1.1.1c 的库。如果你的机器上还有别的程序依赖 3.x,这一步要想清楚:要么不加全局配置,改成在编译时用-Wl,-rpath把路径写进可执行文件;要么加了之后盯紧那些依赖系统 OpenSSL 的服务。
# 另一种做法:不改全局,只让当前工程能找到 export PKG_CONFIG_PATH=/usr/local/openssl111c/lib/pkgconfig:$PKG_CONFIG_PATH pkg-config --modversion openssl # 应输出 1.1.1cPKG_CONFIG_PATH只影响当前 shell 里的 pkg-config 查询,范围小、风险低。CMake 工程里配合OPENSSL_ROOT_DIR用:
# 指定根目录,find_package 会优先在这里找 set(OPENSSL_ROOT_DIR "/usr/local/openssl111c") find_package(OpenSSL 1.1.1 REQUIRED) message(STATUS "OpenSSL: ${OPENSSL_VERSION} ${OPENSSL_LIBRARIES}")find_package拿到OPENSSL_ROOT_DIR之后,会去<root>/include找头文件、去<root>/lib找库,找到就锁死版本,不会再去/usr里翻。打印一下版本号是好习惯,能立刻确认它到底找的是哪一份。
4. Windows 编译 OpenSSL_1_1_1c:Perl 和 NASM 的坑
4.1 环境变量与工具链准备
Windows 上的失败,八成出在环境上:Perl 装了但 PATH 没配,nmake 在但 vcvars 没初始化,NASM 装了但版本太低。我一般写一个 bat 把顺序固定死:
@echo off REM 1. 把 Perl 和 NASM 放进 PATH,路径按自己机器改 set PATH=C:\dev\perl\bin;C:\dev\nasm;%PATH% REM 2. 初始化 VS 的 x64 编译环境,之后才有 cl.exe 和 nmake.exe call "%VS2017INSTALLDIR%\VC\Auxiliary\Build\vcvars64.bat" REM 3. 确认三件套都在 where perl where nasm where nmake顺序不能反。vcvars64.bat会改写一批环境变量,如果先 call 再改 PATH,有可能把刚设好的路径又盖掉。where是最后的自检,三个命令都返回路径才算环境齐了。如果nasm那行没输出,要么没装,要么路径写错——这种情况下先不折腾,编译时加no-asm直接绕过。
4.2 perl Configure 的 target 怎么选
target 选错是 Windows 上第二高频的问题。1.1.1c 支持一堆 VC 相关 target,常用的就两个:
| target | 含义 | 产物目录 |
|---|---|---|
| VC-WIN64A | x64,汇编优化 | out64 或根目录 |
| VC-WIN32 | x86,32 位 | 独立目录 |
64 位机器上编 64 位库,用VC-WIN64A。写VC-WIN64(不带 A)会走到另一条路径上,部分版本里行为不一致,别踩这个字母。
REM 在源码目录里执行,指定安装前缀和配置目录 perl Configure VC-WIN64A ^ --prefix=D:\sdk\openssl111c ^ --openssldir=D:\sdk\openssl111c\ssl ^ no-testsVC-WIN64A:目标平台,决定用哪套汇编和编译选项。--prefix/--openssldir:和 Linux 侧含义一致,指到独立目录。no-tests:不构建测试程序,能省不少编译时间。要跑验证的话把这条去掉。- 没装 NASM 就在后面补
no-asm,其余参数不动。
这里有个细节:perl Configure一旦跑过一次,源码目录里会留下配置痕迹。想换参数重来,先nmake clean,不行就删掉Makefile和out32*、out64*这些中间目录,重新解包最干净。直接改参数重跑,有时候会残留上一次的宏定义。
4.3 nmake 与产物清单
配置完成后用 nmake,不是 Linux 那套 make:
REM 编译,开多进程 nmake -j8 REM 可选:跑测试,确认库可用 nmake test REM 安装到前缀目录 nmake install编完在安装目录里能看到这些文件,缺一个都说明流程有问题:
| 文件 | 用途 |
|---|---|
| openssl.exe | 命令行工具,验证用 |
| libcrypto-1_1-x64.dll | 加密算法动态库 |
| libssl-1_1-x64.dll | TLS 协议动态库 |
| libcrypto.lib / libssl.lib | 导入库,链接时用 |
| include/openssl/*.h | 头文件 |
注意导入库和 DLL 是一对的:链接时找.lib,运行时找.dll,两个都要进部署包。只拷 DLL 不拷 lib,编得过跑不了;反过来只拷 lib 不拷 DLL,程序启动直接弹缺库提示。
工程里引用时,带头文件目录和库目录,然后链两个库:
REM 编译自己的程序,把 SDK 路径喂进去 cl /I D:\sdk\openssl111c\include main.c ^ /link /LIBPATH:D:\sdk\openssl111c\lib libssl.lib libcrypto.lib/I是头文件搜索路径,/LIBPATH是库搜索路径,两个库的链接顺序有讲究:libssl 在前,libcrypto 在后,反了会遇到LNK2019一堆未解析符号,看着像库没链上,其实是顺序问题。
5. 避坑与排查:五个真实翻车现场
5.1 编完了,但openssl version -a还是旧版本
现象:明明装到了/usr/local/openssl111c,敲openssl version -a显示的还是系统那个 3.x,OPENSSLDIR指向/usr/lib/ssl。
原因:PATH 里/usr/bin排在前面,shell 找到的是系统自带的那个可执行文件。库编了新版本,命令行工具没换。
解决:用绝对路径确认新版本能跑,再决定要不要改 PATH。
# 用绝对路径调,绕开 PATH 干扰 /usr/local/openssl111c/bin/openssl version -a # 确认没问题后,临时把新目录提到最前 export PATH=/usr/local/openssl111c/bin:$PATH长期方案是在 shell 的 profile 里加这一行,但我一般不建议全局改 PATH。机器上其他脚本可能依赖系统的openssl命令,你改了之后那些脚本行为会变。要用新版本的时候显式写全路径更稳妥。
5.2 编译时报一堆undefined reference to SSL_xxx
现象:自己的 C 程序编译通过,链接阶段报大量undefined reference to 'SSL_library_init'、undefined reference to 'OPENSSL_init_ssl'。
原因:编译时用的是 1.1.1c 的头文件(-I指定了),链接时用的却是系统 /usr/lib 下的旧库,或者-lssl -lcrypto的顺序写反了。头文件和库版本必须配套,混用是这类报错的头号来源。
解决:先查链接命令里库的搜索路径和顺序。
# 显式指定库路径,并检查链接结果 gcc main.c -o app \ -I/usr/local/openssl111c/include \ -L/usr/local/openssl111c/lib \ -lssl -lcrypto # 编完立刻看它到底依赖哪个库 ldd app | grep -E "ssl|crypto"ldd输出里如果出现/usr/lib/x86_64-linux-gnu/libssl.so.3,说明链接还是走了系统库,-L没生效或者被别的-L覆盖了。加-Wl,-rpath,/usr/local/openssl111c/lib把运行时路径也钉死,能省掉下一节的问题。
5.3 运行时提示error while loading shared libraries: libssl.so.1.1
现象:编译链接全过,一运行就报找不到共享库,或报version 'OPENSSL_1_1_1c' not found。
原因:动态链接器在运行时找不到/usr/local/openssl111c/lib,或者找到了别的同名 so 但符号版本对不上。前一种是没有 ldconfig 或 rpath,后一种是系统里存在多份 libssl.so.1.1。
解决:三条路,按侵入性从低到高。
# 方案一:只对当前进程生效,排查用 LD_LIBRARY_PATH=/usr/local/openssl111c/lib ./app # 方案二:编进可执行文件,随程序走 gcc main.c -o app -L/usr/local/openssl111c/lib -lssl -lcrypto \ -Wl,-rpath,/usr/local/openssl111c/lib # 方案三:全局注册,影响面最大 sudo ldconfig方案二是我最推荐的,rpath 写在可执行文件里,换台机器只要库路径一致就直接能跑,不用额外配环境变量。方案三适合这台机器上所有程序都该用新版本的场景,用之前先ldconfig -p | grep libssl看一眼有没有冲突。
5.4 Windows 下 nmake 报NASM not found
现象:perl Configure VC-WIN64A能过,nmake一上来就提示找不到 nasm,或者汇编相关的构建规则直接失败。
原因:NASM 没装、装了但没进 PATH,或者版本太老不支持 1.1.1c 用的汇编语法。
解决:两个选择,能装就装,不能装就绕。
REM 选择一:确认 nasm 在 PATH 里 where nasm nasm -v REM 选择二:重配一遍,关掉汇编 nmake clean perl Configure VC-WIN64A no-asm --prefix=D:\sdk\openssl111c nmakeno-asm编出来的库功能完全一样,只是少了汇编优化,速度略慢。在开发机上跑业务逻辑,这点差异基本感知不到;如果是给生产环境做性能敏感的加解密,还是把 NASM 装上。
5.5 32 位和 64 位混着编,运行时报架构不匹配
现象:Windows 上编出来是 x86 的库,自己的程序是 x64,链接时报模块计算机类型"x86"与目标计算机类型"x64"冲突。
原因:perl Configure用的 target 是VC-WIN32,但编译自己程序时用的是 x64 工具链;或者反过来,vcvars 调的是 32 位的那个 bat。
解决:两边对齐。编库时用VC-WIN64A,编业务程序时调vcvars64.bat,别用vcvars32.bat。已经编错的话,nmake clean之后重新perl Configure,不能只改一半,DLL 和 lib 必须是同一架构。
6. 验证:三条命令确认这份编译文件真能用
装完最怕的是「看起来装好了」。我一般按三层验证,从命令行工具到库,再到实际链接。
第一层,命令行自报家门:
# 绝对路径调用,看 built on 和 compiler 两行 /usr/local/openssl111c/bin/openssl version -a输出里built on应该是你编译当天的时间,compiler是你用的 gcc 版本,OPENSSLDIR指向/usr/local/openssl111c/ssl。这三项任意一项不对,说明装的根本不是你以为的那份。
第二层,写个十几行的程序,同时打印头文件版本和运行时版本:
/* ver.c:头文件版本和运行时版本必须一致 */ #include <stdio.h> #include <openssl/ssl.h> #include <openssl/crypto.h> int main(void) { printf("header : %s\n", OPENSSL_VERSION_TEXT); printf("runtime: %s\n", OpenSSL_version(OPENSSL_VERSION)); return 0; }gcc ver.c -o ver \ -I/usr/local/openssl111c/include \ -L/usr/local/openssl111c/lib \ -lssl -lcrypto \ -Wl,-rpath,/usr/local/openssl111c/lib ./ver两行输出应该都带1.1.1c。如果 header 是 1.1.1c、runtime 是别的版本,就是第 5 章说的头文件与库不配套,回去查-I和-L是不是指到了同一个前缀。
第三层,Windows 上查依赖:
REM 列出 exe 依赖的动态库,确认指向自己编的那份 dumpbin /dependents D:\sdk\openssl111c\bin\openssl.exe输出的 DLL 列表里出现libssl-1_1-x64.dll和libcrypto-1_1-x64.dll,并且路径能对上你的安装目录,就算通过。
我把这套检查整理成一张对照表,每次装完照着一行行过:
| 检查项 | 命令 | 通过标准 |
|---|---|---|
| 版本号 | openssl version | 输出 1.1.1c |
| 构建信息 | openssl version -a | built on 为编译当天 |
| 配置目录 | openssl version -d | 指向自定义前缀下的 ssl |
| 头文件与运行库 | 编译 ver.c 并执行 | 两行输出版本一致 |
| 动态依赖 | ldd app/dumpbin | 指向自定义库目录 |
有个血泪教训:早期我给一台构建机装完,只跑了openssl version,看着是 1.1.1c 就收工了。结果工程里另一处find_package走的是系统路径,编出来的程序运行时加载了 3.x 的库,跑十几分钟才在某个握手环节崩溃,日志里全是看不懂的符号冲突。从那以后我每次装完都强制走一遍上表,尤其是ldd那一行,它才是真正说明「你的程序连的是哪份库」的黑匣子。希望你不用靠一次线上事故来记住这一点,也希望这套流程帮到你。
本文还有配套的精品资源,点击获取