news 2026/10/17 17:59:42

mbedtls RSA签名验签实战:从密钥加载到避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
mbedtls RSA签名验签实战:从密钥加载到避坑指南

简介:这份资源是面向嵌入式与信息安全开发者的 mbedtls RSA 签名验签实战示例,聚焦数字证书场景下的非对称加密应用。它解决的是如何在资源受限环境中用轻量级 C 库完成密钥生成、哈希计算、私钥签名与公钥验证的完整链路问题,适合具备一定 C 语言与密码学基础、希望将 RSA 签名验签落地到实际项目的开发者参考。压缩包共 111 个文件,以 28 个 h 头文件、25 个 c 源文件为核心,配合 obj 编译产物、cmake 构建脚本、make 与 txt 配置说明等,整体约 640KB,结构紧凑便于快速编译调试。目前已有 504 人学习下载。资源内含 main.c 主流程、CMakeLists.txt 构建配置及 crypto 库目录,覆盖 mbedtls_rsa_gen_key、mbedtls_sha256、mbedtls_rsa_pkcs1_sign 与 verify 等关键接口调用,并涉及 X.509 证书解析,可帮助读者理解非对称加密在数字证书中的实际运用,掌握签名验签的排错思路与工程组织方式。

1. 从一次签名失败说起:mbedtls 的 RSA 签名验签到底怎么落地

嵌入式项目里做安全通信,绕不开数字签名。我见过太多团队在 PC 上跑通了 OpenSSL 的 RSA 签名验签,一移植到 MCU 上就翻车——要么是内存不够,要么是随机数源没接对,要么是填充模式选错导致验签永远返回失败。mbedtls 就是为这类场景准备的:它足够小,能在资源受限的环境里跑起来,同时把 RSA、X.509、PKCS#1 这些密码学原语都封装好了。但封装好不等于用对,RSA 签名验签这条链路上,填充方式、哈希算法、密钥格式、随机数源,任何一个环节对不上,结果就是签名失败或者验签不通过。这篇笔记拆的是一个 mbedtls 实现 RSA 签名验签的 demo,重点不在讲 RSA 数学原理,而在把「怎么把签名验签跑通、跑对、跑稳」这件事说清楚。如果你正在做嵌入式安全、设备证书认证、固件签名校验这类活,这份东西能帮你省掉不少翻文档的时间。

2. RSA 签名验签在 mbedtls 里的实现骨架:从密钥加载到结果校验

2.1 为什么选 mbedtls 而不是 OpenSSL

先说选型。在嵌入式场景里,OpenSSL 的体积和依赖链基本劝退,尤其是要跑在 Cortex-M 系列或者 RTOS 上的时候。mbedtls 的设计目标就是可裁剪、可移植,编译出来可以只有几十 KB,而且不依赖操作系统。它的 API 风格偏底层,但换来的是对内存和行为的完全控制。RSA 签名验签这块,mbedtls 提供了mbedtls_rsa_context这个结构体来承载密钥和运算上下文,配合mbedtls_pk_*系列接口可以统一处理不同密钥类型。常见做法是:用mbedtls_pk_parse_key加载私钥做签名,用mbedtls_pk_parse_public_key加载公钥做验签,中间通过mbedtls_pk_sign和mbedtls_pk_verify完成操作。这套接口的好处是,如果你后面要换成 ECDSA,上层调用逻辑几乎不用大改。

但要注意,mbedtls 的版本差异在这块挺明显。2.x 和 3.x 在mbedtls_rsa_*接口上有变动,比如mbedtls_rsa_pkcs1_sign在 3.x 里参数和返回值处理都有调整。我一般会先确认工程里用的是哪个大版本,再决定用哪套 API。如果你拿到的 demo 是基于 2.x 写的,直接扔到 3.x 环境里编译,大概率会报一堆 deprecated 警告甚至链接错误。

2.2 签名流程的代码骨架

下面这段是签名侧的核心逻辑,基于 mbedtls 3.x 的接口风格。假设你已经把私钥读进了一个 buffer,格式是 PEM。

#include "mbedtls/pk.h" #include "mbedtls/rsa.h" #include "mbedtls/sha256.h" #include "mbedtls/ctr_drbg.h" #include "mbedtls/entropy.h" int rsa_sign_demo(const unsigned char *key_pem, size_t key_len, const unsigned char *msg, size_t msg_len, unsigned char *sig, size_t *sig_len) { int ret; mbedtls_pk_context pk; mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context ctr_drbg; unsigned char hash[32]; mbedtls_pk_init(&pk); mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_init(&ctr_drbg); // 初始化随机数源,签名本身不需要随机数,但 mbedtls 内部可能用到 ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); if (ret != 0) goto cleanup; // 加载私钥 ret = mbedtls_pk_parse_key(&pk, key_pem, key_len, NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg); if (ret != 0) goto cleanup; // 先对消息做 SHA-256 ret = mbedtls_sha256(msg, msg_len, hash, 0); if (ret != 0) goto cleanup; // 用私钥签名,指定 PKCS#1 v1.5 填充和 SHA-256 *sig_len = mbedtls_pk_get_len(&pk); ret = mbedtls_pk_sign(&pk, MBEDTLS_MD_SHA256, hash, 0, sig, sig_len, mbedtls_ctr_drbg_random, &ctr_drbg); cleanup: mbedtls_pk_free(&pk); mbedtls_ctr_drbg_free(&ctr_drbg); mbedtls_entropy_free(&entropy); return ret; }

这段代码的逻辑链条是:初始化上下文 → 播种随机数 → 解析私钥 → 对消息做哈希 → 用私钥对哈希值签名。几个关键参数说明一下。MBEDTLS_MD_SHA256指定了签名时使用的哈希算法,这个必须和验签侧一致,否则验签必挂。mbedtls_pk_sign的第四个参数是哈希值的长度,传 0 表示让 mbedtls 根据算法自动判断。sig_len传入时是 buffer 大小,返回时是实际签名长度,RSA-2048 的话就是 256 字节。随机数源这块,虽然 PKCS#1 v1.5 签名本身不需要随机数,但 mbedtls 的 API 签名要求传入 RNG 回调,所以还是得初始化一个。

2.3 验签流程的代码骨架

验签侧的逻辑和签名对称,但用的是公钥。

int rsa_verify_demo(const unsigned char *pub_pem, size_t pub_len, const unsigned char *msg, size_t msg_len, const unsigned char *sig, size_t sig_len) { int ret; mbedtls_pk_context pk; unsigned char hash[32]; mbedtls_pk_init(&pk); // 加载公钥 ret = mbedtls_pk_parse_public_key(&pk, pub_pem, pub_len); if (ret != 0) goto cleanup; // 对原始消息做同样的哈希 ret = mbedtls_sha256(msg, msg_len, hash, 0); if (ret != 0) goto cleanup; // 验签 ret = mbedtls_pk_verify(&pk, MBEDTLS_MD_SHA256, hash, 0, sig, sig_len); cleanup: mbedtls_pk_free(&pk); return ret; }

验签的返回值很关键:0 表示验签通过,非 0 表示失败。常见的失败码包括MBEDTLS_ERR_RSA_VERIFY_FAILED,这个通常意味着签名和公钥不匹配,或者哈希算法不一致,或者填充模式不对。调试的时候不要只看「失败」两个字,把返回码打出来,对着 mbedtls 的错误码表查,能省很多时间。

3. 把 demo 跑起来:编译配置、密钥生成与端到端验证

3.1 编译配置里必须打开的宏

mbedtls 默认配置不一定包含 RSA 和 PKCS#1 相关的功能,尤其是如果你用的是裁剪过的配置文件。下面这几个宏必须确认打开:

宏名称作用不开的后果
MBEDTLS_RSA_C启用 RSA 算法所有 RSA 接口不可用
MBEDTLS_PKCS1_V15启用 PKCS#1 v1.5 填充签名验签直接失败
MBEDTLS_PK_C启用公钥抽象层mbedtls_pk_*接口不可用
MBEDTLS_SHA256_C启用 SHA-256哈希计算失败
MBEDTLS_CTR_DRBG_C启用 CTR_DRBG 随机数上下文初始化失败
MBEDTLS_ENTROPY_C启用熵源随机数播种失败
MBEDTLS_BIGNUM_C启用大数运算RSA 底层运算不可用
MBEDTLS_ASN1_PARSE_C启用 ASN.1 解析PEM/DER 密钥解析失败

如果你用的是mbedtls_config.h或者config.h,直接搜这些宏,确认没有被注释掉。有些 demo 为了减小体积会把MBEDTLS_PKCS1_V15关掉,只留 PSS,这时候你用 v1.5 的接口就会返回MBEDTLS_ERR_RSA_INVALID_PADDING。

3.2 生成测试用的 RSA 密钥对

跑 demo 之前得有一对密钥。用 openssl 命令行生成就行,注意格式要和 mbedtls 的解析接口匹配。

# 生成 2048 位私钥,PEM 格式 openssl genrsa -out private.pem 2048 # 从私钥导出公钥 openssl rsa -in private.pem -pubout -out public.pem # 查看私钥内容,确认是 PKCS#1 还是 PKCS#8 head -1 private.pem

head -1的输出如果是-----BEGIN RSA PRIVATE KEY-----,说明是 PKCS#1 格式;如果是-----BEGIN PRIVATE KEY-----,说明是 PKCS#8 格式。mbedtls 的mbedtls_pk_parse_key两种都能解析,但如果你用的是更底层的mbedtls_rsa_parse_key,那就只认 PKCS#1。我一般建议统一用mbedtls_pk_*系列接口,省得在格式上纠结。

公钥这边,mbedtls_pk_parse_public_key能处理-----BEGIN PUBLIC KEY-----这种 SubjectPublicKeyInfo 格式,也能处理-----BEGIN RSA PUBLIC KEY-----这种 PKCS#1 格式。但如果你拿到的公钥是证书里的,那就得先用mbedtls_x509_crt_parse解析证书,再从证书结构里取公钥。

3.3 端到端验证的完整流程

把签名和验签串起来跑一遍,确认整条链路是通的。下面是一个简化的 main 函数框架:

int main(void) { unsigned char sig[256]; size_t sig_len = sizeof(sig); const char *msg = "hello mbedtls rsa"; int ret; // 读取 private.pem 和 public.pem 到 buffer // 这里省略文件读取代码,假设 key_buf 和 pub_buf 已经填好 ret = rsa_sign_demo(key_buf, key_len, (const unsigned char *)msg, strlen(msg), sig, &sig_len); if (ret != 0) { printf("sign failed: -0x%04X\n", -ret); return -1; } printf("sign ok, sig_len=%zu\n", sig_len); ret = rsa_verify_demo(pub_buf, pub_len, (const unsigned char *)msg, strlen(msg), sig, sig_len); if (ret != 0) { printf("verify failed: -0x%04X\n", -ret); return -1; } printf("verify ok\n"); return 0; }

跑通之后你会看到sign ok和verify ok。如果验签失败,先把签名结果用openssl rsautl -verify或者openssl pkeyutl -verify交叉验证一下,确认是 mbedtls 的问题还是密钥本身的问题。交叉验证这步很关键,能帮你快速定位问题出在哪个环节。

4. 避坑指南:RSA 签名验签里最容易翻车的五个点

4.1 哈希算法不一致导致验签永远失败

现象:签名返回成功,验签返回MBEDTLS_ERR_RSA_VERIFY_FAILED,换密钥、换消息都一样。

原因:签名时用了 SHA-256,验签时用了 SHA-1,或者签名时传的是原始消息,验签时传的是哈希值。mbedtls 的mbedtls_pk_sign和mbedtls_pk_verify都要求传入哈希值而不是原始消息,如果你在签名侧传了原始消息,验签侧传了哈希值,两边对不上。

解决:统一在调用mbedtls_pk_sign和mbedtls_pk_verify之前先做哈希,并且确保两边的MBEDTLS_MD_*参数完全一致。建议把哈希算法定义成一个宏,两边共用。

4.2 密钥格式不匹配导致解析失败

现象:mbedtls_pk_parse_key返回MBEDTLS_ERR_PK_KEY_INVALID_FORMAT或MBEDTLS_ERR_PK_UNKNOWN_PK_ALG。

原因:私钥是 PKCS#8 格式,但代码里用的是mbedtls_rsa_parse_key;或者私钥是加密的,但没提供密码;或者 PEM 头尾有多余的空格或换行。

解决:优先用mbedtls_pk_parse_key而不是mbedtls_rsa_parse_key,前者兼容性更好。如果是加密私钥,第四个参数传密码,长度传密码长度。另外注意 PEM 文件读取时不要漏掉末尾的换行,有些解析器对格式很敏感。

4.3 随机数源没接对导致初始化失败

现象:mbedtls_ctr_drbg_seed返回MBEDTLS_ERR_ENTROPY_SOURCE_FAILED,或者整个签名流程在初始化阶段就挂了。

原因:嵌入式环境里没有默认的熵源,mbedtls_entropy_func拿不到足够的随机性。或者mbedtls_entropy_init之后没有添加自定义熵源。

解决:在嵌入式平台上,通常需要添加一个硬件随机数源作为熵源,比如mbedtls_entropy_add_source配合芯片的 TRNG。如果平台没有 TRNG,至少要用一个带时间抖动的软件熵源。PC 上跑 demo 一般不会遇到这个问题,但移植到板子上就是高频翻车点。

4.4 签名长度计算错误导致 buffer 溢出

现象:签名返回MBEDTLS_ERR_RSA_BAD_INPUT_DATA,或者程序跑飞。

原因:sig_len传入时是 buffer 的实际大小,如果 buffer 小于 RSA 密钥长度(比如 RSA-2048 需要 256 字节),mbedtls 会直接报错。或者签名成功后没有更新sig_len,后续用错误的长度去验签。

解决:分配签名 buffer 时用mbedtls_pk_get_len(&pk)获取密钥长度,按这个长度分配。签名调用后sig_len会被更新为实际签名长度,验签时用这个值。不要硬编码 256 或 128,密钥长度变了代码就废了。

4.5 填充模式选错导致兼容性问题

现象:mbedtls 自己签自己验能过,但和 OpenSSL 或者其他库交叉验签就失败。

原因:mbedtls 默认用 PKCS#1 v1.5 填充,但有些场景要求用 PSS。或者签名侧用了 v1.5,验签侧配置里只开了 PSS。

解决:确认双方的填充模式一致。mbedtls 里 PKCS#1 v1.5 对应MBEDTLS_RSA_PKCS_V15,PSS 对应MBEDTLS_RSA_PKCS_V21。如果要做跨库兼容,建议用 v1.5,兼容性最好。PSS 虽然更安全,但参数配置更复杂,容易在盐长度上翻车。

5. 进阶技巧:用 X.509 证书链做验签和批量校验

5.1 从证书里提取公钥做验签

实际项目里,公钥很少单独传,通常是放在 X.509 证书里。mbedtls 提供了完整的证书解析接口,可以从证书里取出公钥再做验签。下面这段代码演示了从 PEM 证书里提取公钥并验签的流程:

#include "mbedtls/x509_crt.h" int verify_with_cert(const unsigned char *cert_pem, size_t cert_len, const unsigned char *msg, size_t msg_len, const unsigned char *sig, size_t sig_len) { int ret; mbedtls_x509_crt crt; unsigned char hash[32]; mbedtls_x509_crt_init(&crt); ret = mbedtls_x509_crt_parse(&crt, cert_pem, cert_len); if (ret != 0) goto cleanup; ret = mbedtls_sha256(msg, msg_len, hash, 0); if (ret != 0) goto cleanup; // 直接用证书里的公钥验签 ret = mbedtls_pk_verify(&crt.pk, MBEDTLS_MD_SHA256, hash, 0, sig, sig_len); cleanup: mbedtls_x509_crt_free(&crt); return ret; }

这里的关键是crt.pk这个成员,它直接承载了证书里的公钥上下文。mbedtls_x509_crt_parse支持 PEM 和 DER 两种格式,PEM 的话传入的长度要包含末尾的\0,DER 的话传实际字节数。这个细节很容易忽略,导致解析返回MBEDTLS_ERR_X509_INVALID_FORMAT。

5.2 证书链校验的配置要点

如果你要做完整的证书链校验,而不只是用证书里的公钥验签,那就需要配置 CA 链和校验参数。mbedtls 的mbedtls_x509_crt_verify接口可以完成这个工作,但配置项比较多。下面是一个典型的校验配置:

uint32_t flags; mbedtls_x509_crt ca_chain; mbedtls_x509_crt_init(&ca_chain); // 加载 CA 证书链 ret = mbedtls_x509_crt_parse(&ca_chain, ca_pem, ca_len); if (ret != 0) goto cleanup; // 校验设备证书 ret = mbedtls_x509_crt_verify(&device_crt, &ca_chain, NULL, NULL, &flags, NULL, NULL); if (ret != 0) { printf("cert verify failed, flags=0x%08X\n", flags); goto cleanup; }

flags这个参数很重要,校验失败时它会告诉你具体是哪一项没过——过期、签名不匹配、CA 不在链里,都能从 flags 的位掩码里看出来。我一般会在调试阶段把 flags 打出来,对着MBEDTLS_X509_BADCERT_*系列宏查,比盲猜快得多。

5.3 批量验签时的性能考量

如果你要在设备上做批量签名校验,比如固件更新时校验多个分片,RSA 验签的性能会成为瓶颈。RSA-2048 在 Cortex-M4 上单次验签约 10-30ms,签名更慢,可能到 100ms 以上。优化方向有几个:一是用公钥指数小的密钥(比如 e=65537 是默认值,已经比较快了);二是如果平台支持硬件加速,把 RSA 运算卸载到硬件引擎;三是考虑换 ECDSA,同等安全强度下密钥更短、运算更快。但换算法意味着证书和签名格式都要改,迁移成本不低,得权衡。

5.4 一个我踩过的坑

早期做设备证书认证的时候,我把证书链校验和签名验签混在一起做,结果发现有些设备证书能通过链校验,但签名验签失败。排查了半天才发现,设备证书里的公钥和实际用来签名的私钥不是一对——产线烧录的时候把证书和私钥搞混了。从那以后我每次做证书相关的集成,都会强制走一遍「用证书里的公钥验签、用私钥签名、交叉验证」的流程,确认密钥对是匹配的再往下做。这个习惯帮我省了好几次返工。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 17:54:19

IDEA 终于也能爽用 TaoToken 了!!

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 17:52:42

Dify节点配置实战:从流程梳理到调试技巧

1. 节点配置前,先想清楚这三件事我对Dify节点配置一直有个观点:会点按钮不叫会配置,真正上手之后才知道,节点配置的难点从来不在“填参数”这个动作上,而在配置之前的思路梳理。这个标题里的项目,当初就是从…

作者头像 李华
网站建设 2026/10/17 17:51:33

Git Diff驱动的开源代码审查协议栈:CLI+LLM Agent落地实践

1. 这不是另一个“AI代码审查工具”,而是一套可落地的开源协作范式最近两周,我连续收到7个不同团队的私信,问同一个问题:“你们用的 open-code-review 是怎么跑起来的?不是 GitHub Copilot 那种黑盒,也不是…

作者头像 李华
网站建设 2026/10/17 17:48:07

YOLO v1到v5核心解析:损失函数、锚框与多尺度检测实践

简介:面向目标检测初学者和算法研究者的YOLO系列专题解读文档,围绕YOLOv1至YOLOv5的完整演进脉络,系统梳理算法核心原理、网络设计与实现细节。包内含1个docx文档,约4MB,以文字、公式结合代码注解形式呈现,…

作者头像 李华
网站建设 2026/10/17 17:47:32

Excel最优化决策模型实战:线性规划、整数规划与敏感性分析

简介:课件聚焦经济管理中的计算机应用第八章最优化决策模型,面向经管类专业学生、教师以及需要借助Excel完成量化分析的从业者,系统讲解如何用数学建模和规划求解工具在资源受限条件下寻找最优方案。内容从最优化问题的定义、分类与数学模型入…

作者头像 李华
网站建设 2026/10/17 17:45:55

Claude提示词工程:四大支柱构建高可靠提示策略

简介:本资源是一份面向AI提示工程学习者与Claude深度使用者的「加强版Claude提示词」实践指南,聚焦于提升模型思考质量与响应适应性。它系统拆解了Claude应遵循的核心思考协议,包括强制性的自然流式内心独白(以thinking代码块呈现…

作者头像 李华