3. 密码库源码结构解析

裁剪密码库之前,必须先回答一个更基础的问题:这个库到底由哪些部分组成,哪些部分可以拆掉,拆掉之后谁会受影响。mbedTLS、wolfSSL、OpenSSL 虽然都提供 TLS 与密码算法能力,但它们的源码组织哲学差异很大,直接决定了裁剪的难度和手段。本节从模块划分、依赖关系和可裁剪边界三个角度逐一拆解,并给出可操作的判断方法。

三种库的模块划分风格

mbedTLS 采用“算法层—X.509 层—TLS 层”三层结构,目录划分非常直白:library/ 下每个算法基本对应一个 .c 文件,如 aes.c、sha256.c、rsa.c、ecp.c;include/mbedtls/ 下是配套头文件;mbedtls_config.h 是唯一的裁剪开关中心。它的依赖关系是单向的:TLS 依赖 X.509,X.509 依赖算法层,算法层之间只有少量交叉(如 rsa.c 调用 bignum.c)。这种“扁平算法 + 集中配置”的设计,使 mbedTLS 成为最容易按功能裁剪的库。

wolfSSL 的源码更集中,核心算法与 TLS 逻辑大量放在 wolfcrypt/src/ 与 src/ 中,通过 user_settings.h 与 --enable-* 宏控制编译。它的特点是“宏驱动裁剪”:一个 WOLFSSL_AES_128 或 NO_RSA 宏就能决定对应代码是否参与编译。wolfSSL 的依赖耦合比 mbedTLS 略高,例如 TLS 层与密码层共享较多内部结构体,裁剪时需要同时关注 wolfssl/wolfcrypt/settings.h 的宏联动。

OpenSSL 则是“分层 + 引擎 + provider”的复杂体系。3.x 之后引入 provider 架构,算法实现被封装进 providers/,通过 OSSL_PROVIDER 在运行时加载;crypto/ 下是底层原语,ssl/ 是协议层。它的可裁剪边界不像前两者那样由单个头文件决定,而是通过 Configure 的 no-* 选项在构建期剔除模块,例如 no-rsa、no-ec、no-legacy。OpenSSL 的依赖图最复杂,裁剪时最容易出现“关掉一个算法却导致 TLS 握手路径编译失败”的连锁反应。

三种密码库的模块分层与依赖方向 箭头表示“上层依赖下层”;红色虚线表示裁剪时最易断裂的耦合点 mbedTLS 扁平算法 + 集中配置 TLS 层 ssl_tls.c / ssl_srv.c X.509 层 x509_crt.c / x509_crl.c 算法层 aes.c / sha256.c / rsa.c / ecp.c 大数/工具层 bignum.c / platform_util.c 裁剪开关 mbedtls_config.h wolfSSL 宏驱动裁剪 + 共享内部结构 TLS 协议层 src/ssl.c / tls.c 密码原语层 wolfcrypt/src/aes.c / rsa.c 共享内部结构 wolfssl/wolfcrypt/types.h 裁剪开关 user_settings.h / --enable-* OpenSSL 3.x 分层 + provider 运行时加载 SSL/TLS 层 ssl/statem / ssl/record Provider 层 providers/implementations Crypto 原语层 crypto/aes / crypto/rsa 裁剪开关 Configure no-* / OSSL_PROVIDER 算法↔TLS 共享结构 provider 与 crypto 交叉 裁剪难度:mbedTLS(低,单向依赖) < wolfSSL(中,宏联动) < OpenSSL(高,运行时 provider 与构建期 no-* 交织)

依赖关系的三种典型形态

理解依赖关系,关键看“谁调用谁”和“谁定义谁”。mbedTLS 的依赖是编译期单向依赖:ssl_tls.c 调用 x509_crt.c,后者调用 rsa.c 和 ecp.c,而算法层之间几乎不反向调用 TLS。这意味着只要保留被 TLS 路径实际引用的算法,其余算法可以整文件剔除。例如关闭 MBEDTLS_ECDSA_C 后,只要 TLS 配置中不再协商 ECDSA 套件,链接就不会报错。

wolfSSL 的依赖是宏条件依赖。同一个 ssl.c 中,不同函数体被 #ifdef HAVE_AESGCM、#ifndef NO_RSA 等宏包裹。裁剪时不是删文件,而是让宏组合成立。风险在于宏之间存在隐含约束:例如启用 HAVE_ECC 但关闭 HAVE_SUPPORTED_CURVES,可能在握手时找不到曲线列表。因此 wolfSSL 裁剪必须做“宏组合编译验证”,而不是只改一个开关。

OpenSSL 的依赖是构建期 + 运行期双重依赖。Configure no-ec 会在编译期移除 EC 相关目标文件,但 provider 架构下,某些算法实现可能仍被默认 provider 引用。若同时使用 OSSL_PROVIDER_load(NULL, "default"),运行期加载的 provider 会决定实际可用算法。裁剪 OpenSSL 时,必须同时检查 Configure 选项、openssl.cnf 中的 provider 配置,以及应用是否显式加载了某个 provider。

可裁剪边界的判断方法

判断一个模块能否裁剪,可以按以下顺序操作。第一步,确定目标功能集合:例如只需要 TLS 1.2 客户端、AES-128-GCM、SHA-256、ECDHE-P256、RSA 签名验证。第二步,从入口函数反向追踪调用链:以 mbedtls_ssl_handshake 为起点,沿 ssl_tls.c → ssl_cli.c → x509_crt.c → pk.c → rsa.c / ecp.c → bignum.c 逐层标记被引用的符号。第三步,对照配置宏,把未被引用的算法宏关闭,重新编译并观察链接错误。第四步,对链接错误逐个判断:是“确实不需要但被间接引用”,还是“目标功能实际需要但被误关”。

以 mbedTLS 为例,一个典型的裁剪配置片段如下:

// mbedtls_config.h 裁剪示例(目标:TLS1.2 客户端 + AES-GCM + ECDHE + RSA 验签)
#define MBEDTLS_SSL_CLI_C
#define MBEDTLS_SSL_TLS_C
#define MBEDTLS_SSL_PROTO_TLS1_2
#define MBEDTLS_AES_C
#define MBEDTLS_GCM_C
#define MBEDTLS_SHA256_C
#define MBEDTLS_ECDH_C
#define MBEDTLS_ECP_C
#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
#define MBEDTLS_RSA_C
#define MBEDTLS_PKCS1_V15
#define MBEDTLS_BIGNUM_C
#define MBEDTLS_X509_CRT_PARSE_C
#define MBEDTLS_ENTROPY_C
#define MBEDTLS_CTR_DRBG_C

// 关闭项
#undef MBEDTLS_SSL_SRV_C
#undef MBEDTLS_SSL_PROTO_TLS1_3
#undef MBEDTLS_DES_C
#undef MBEDTLS_CAMELLIA_C
#undef MBEDTLS_BLOWFISH_C
#undef MBEDTLS_XTEA_C
#undef MBEDTLS_MD5_C
#undef MBEDTLS_SHA1_C
#undef MBEDTLS_ECP_DP_SECP384R1_ENABLED
#undef MBEDTLS_ECP_DP_SECP521R1_ENABLED
#undef MBEDTLS_ECP_DP_CURVE25519_ENABLED
#undef MBEDTLS_PKCS12_C
#undef MBEDTLS_PK_PARSE_C
#undef MBEDTLS_PEM_PARSE_C

编译后若出现 undefined reference to mbedtls_sha1_process,说明某条被保留的路径仍引用 SHA-1,常见于旧版 X.509 或 TLS 记录层兼容逻辑。此时应检查 MBEDTLS_X509_USE_C 与 MBEDTLS_SSL_PROTO_TLS1_2 的宏组合,而不是简单把 SHA-1 重新打开。裁剪的目标是“最小可用集合”,不是“能编过就行”。

裁剪边界判断的核心原则:先确定功能集合,再反向追踪符号引用,最后用编译链接结果验证。不要从“删文件”开始,而要从“保留哪些入口”开始。

三种库的裁剪策略对比

维度mbedTLSwolfSSLOpenSSL 3.x
裁剪入口mbedtls_config.huser_settings.h / --enable-*Configure no-* / provider 配置
依赖方向单向、编译期宏条件、编译期构建期 + 运行期
最小 TLS 客户端典型体积约 60–120 KB约 80–150 KB通常 1 MB 以上
主要风险间接符号引用宏组合冲突provider 与 no-* 不一致
验证方式链接错误 + 握手测试宏组合编译 + 握手测试Configure 检查 + provider 加载测试

表中体积为常见嵌入式配置下的量级参考,实际值随编译器优化、是否启用硬件加速、是否保留调试符号而明显变化,不应作为验收指标直接使用。真正可靠的判断依据是:目标功能全部可用、链接无未定义符号、握手与数据收发测试通过、裁剪后二进制体积相比裁剪前有可解释的下降。