RSA 算法的 C语言实现通常比较复杂,但已经有许多密码算法库实现了 RSA 算法,例如OpenSSL、Libgcrypt 和 Botan 等。我们可以在这些库的基础上进行配置或移植,从而快速实现密码算法。但这些库主要面向大量设备进行优化,如通用计算机和服务器。本文需要在嵌入式设备上实现密码算法,因此选择了实现开销更小的密码算法库 BearSSL。BearSSL包含RSA的两种加密方案,分别是PKCS#1 v1.5方案和RSAES-OAEP方案,PKCS#1 v1.5方案是早期方案,存在安全漏洞,RSAES-OAEP使用OAEP填充技术,安全性高。BearSSL还包含两种RSA签名方案,分别是RSASSA-PKCS1-v1_5方案和RSASSA-PSS方案,RSASSA-PKCS1-v1_5是早期方案,有安全漏洞,RSASSA-PSS是目前推荐方案,使用PSS填充技术,引入随机盐值,安全性高。另外,BearSSL还包含还包括一些分组密码加密和杂凑算法相关内容。本文仅进行测试演示,从BearSSL中提取出了容易实现的RSASSA-PKCS1-v1_5方案,虽然该方案存在漏洞,但因此实现简便,资源占用更小,但可以用于安全要求不太高的小型设备,或作为教学使用。我们删减了除RSASSA-PKCS1-v1_5方案外的大量冗余内容,使工程大幅简化,下载地址为:https://download.csdn.net/download/weixin_43261410/91277142,读者可以免费下载。
一、BearSSL 密码算法库
BearSSL是一个轻量级、高性能的SSL/TLS加密库,专为嵌入式系统和资源受限环境设计。与其他通用加密库(如OpenSSL)不同,BearSSL的核心目标是极致的精简与高效,同时不牺牲安全性。它采用模块化设计,允许开发者仅编译所需的加密算法,从而大幅减少代码体积(可小至50KB以下)和内存占用(栈内存通常低于3KB)。这种设计使其非常适合运行在微控制器(如ARM Cortex-M)、物联网设备(IoT)或实时操作系统中。此外,BearSSL严格遵循恒定时间编程原则,所有算法均避免数据依赖的分支和内存访问,有效抵御侧信道攻击(如时序攻击)。这种对安全性和资源效率的平衡,使其成为嵌入式安全通信的首选库之一。
BearSSL在技术实现上有多项创新,尤其是其分层加密算法支持和大整数运算优化。例如,它提供多种大整数实现(i15、i31、i62),分别针对不同硬件平台优化:i15适用于无硬件乘法指令的CPU(如Cortex-M0),i31利用32位CPU的64位乘法加速运算,而i62则针对64位架构(如x86-64)进一步优化模幂运算。此外,BearSSL采用纯C语言编写,无需汇编代码,确保了跨平台兼容性。其API设计也极具特色,例如通过“控制位”(ctl参数)实现恒定时间的条件操作,避免分支预测漏洞。这些设计使其在保持高度可移植性的同时,仍能实现接近硬件的性能。
BearSSL在安全性上毫不妥协,全面支持现代TLS协议(如TLS 1.2和1.3),并实现了前向保密(PFS)和抗降级攻击的机制。其密码套件默认禁用弱算法(如RC4、SHA-1),且支持证书链验证和OCSP装订(OCSP Stapling)。与其他库不同,BearSSL的证书解析器极度精简,仅处理必要的X.509字段,既减少了代码体积,又降低了潜在漏洞风险。此外,它通过了多项密码学标准的验证(如FIPS 140-2的算法测试),并提供了针对侧信道攻击的防护措施(如盲签名、恒定时间模幂运算)。这些特性使其在工业控制、医疗设备等对安全性要求严苛的场景中备受青睐。
二、仅提取i31位的PKCS1签名与验签方法
i31实现是BearSSL中平衡性能和代码大小的优化选择,使用31位无符号整数作为基本运算单元。这种设计充分利用了32位CPU的寄存器宽度,同时避免了符号位的潜在问题。在示例工程中,我们通过精心挑选的源文件实现了最小化的RSA PKCS#1功能,仅包含i31相关运算和必要的辅助函数。
签名过程遵循PKCS#1 v1.5规范,首先使用br_rsa_pkcs1_sig_pad对哈希值进行编码,添加ASN.1算法标识符和填充字节。示例中使用BR_HASH_OID_SHA256常量指定SHA-256算法,这比硬编码OID更安全可靠。填充后的消息随后通过br_rsa_i31_private函数进行实际签名运算,该函数采用CRT优化显著提升性能。
验签过程则相反,先用br_rsa_i31_public解密签名,再用br_rsa_pkcs1_sig_unpad验证填充格式并提取哈希值。值得注意的是,示例中严格检查了所有函数的返回值,这是安全编程的关键实践。验签最后一步将提取的哈希值与原始哈希比较,确保内容的完整性和真实性。
工程中的params.h文件包含了完整的RSA-1024密钥对,采用中国剩余定理格式存储。私钥包含p、q、dp、dq和iq等CRT参数,这些参数预先计算并存储,可显著提升签名速度。公钥部分则包含标准的模数n和公开指数e(通常为65537)。这种密钥表示方式与BearSSL的br_rsa_private_key和br_rsa_public_key结构体完美匹配,便于直接使用。
三、整体工程架构分析
工程采用清晰的模块化结构,通过CMake构建系统管理。顶层CMakeLists.txt明确定义了所有源文件和头文件的依赖关系,确保构建过程的可重复性。项目结构分为include和src两个主要目录,前者存放BearSSL头文件和应用特定的params.h,后者包含精选的i31实现源文件。
核心功能集中在main.c中,该文件实现了完整的签名验签工作流。签名函数sign_data初始化私钥结构并调用br_rsa_i31_pkcs1_sign;验签函数verify_signature类似地配置公钥后调用br_rsa_i31_pkcs1_vrfy。这种封装使主逻辑清晰可读,同时便于复用。密钥材料与业务逻辑分离的设计增强了安全性,方便后续密钥轮换。
构建系统仅包含实现RSA PKCS#1签名验签所需的最小源文件集,如i31_*.c基础运算和rsa_i31_*.c专门实现。这种精确的组件选择体现了BearSSL的模块化优势,最终生成的可执行文件体积显著小于包含完整库的情况。settings.c提供必要的运行时配置,而ccopy.c等辅助函数则确保安全的内存操作。
测试方面,工程使用固定的测试向量进行基础验证。main函数中硬编码的SHA-256哈希值作为输入,通过打印签名前后数据提供了简单的视觉验证手段。虽然这不能替代完整的测试套件,但对于演示和基础验证已经足够。实际项目中可扩展为自动化单元测试,覆盖更多边界情况和错误路径。
四、测试函数实现
测试实现采用了经典的测试模式 - 先执行签名,再验证签名,最后比较结果。main函数中首先打印原始哈希值建立基线,然后调用sign_data生成签名。签名失败会立即终止程序并提示错误,成功则输出128字节的签名数据,每32字节换行以便阅读。
验签阶段将签名作为输入,调用verify_signature函数。该函数不仅检查签名本身的合法性,还通过memcmp确保解出的哈希与原始哈希完全一致。这种双重验证保证了整个流程的正确性。测试输出包含明确的成功/失败指示和详细的十六进制数据,极大便利了调试过程。
测试使用的固定哈希值对应字符串"BearSSL RSA Test"的SHA-256摘要,这提供了确定性的测试基准。实际应用中,哈希值通常来自对实际消息的摘要计算。示例中签名缓冲区(signature)大小固定为128字节(1024位),与密钥长度匹配;哈希输出缓冲区(verify_hash_out)则为32字节,符合SHA-256的输出要求。
错误处理方面,代码检查了所有关键操作的返回值,包括签名和验签函数的输出。这种防御性编程风格对于安全关键代码至关重要。虽然示例中没有实现复杂的错误恢复机制,但简单的立即返回已能防止错误传播。扩展测试可添加无效签名、错误密钥等负面测试案例,进一步验证代码的健壮性。
最后,我们将参数保存在了params.h文件中,这是原工程没有的。密钥参数生成和SHA256的哈希计算可采用工程scripts目录下的两个脚本实现,注意需要在python中通过命令pip install cryptography安装库后运行。
#include <stdio.h>
#include <string.h>
#include "bearssl_rsa.h"
#include "params.h"unsigned char hash_value[] = {0x54, 0xba, 0x1f, 0xdc, 0xe5, 0xa8, 0x9e, 0x0d,0x3e, 0xee, 0x6e, 0x4c, 0x58, 0x74, 0x97, 0x83,0x3b, 0xc3, 0x8c, 0x35, 0x86, 0xff, 0x02, 0x05,0x7d, 0xd6, 0x45, 0x1f, 0xd2, 0xd6, 0xb6, 0x40
};int sign_data(unsigned char *signature) {br_rsa_private_key sk;sk.n_bitlen = 1024;sk.p = RSA_P;sk.plen = sizeof(RSA_P);sk.q = RSA_Q;sk.qlen = sizeof(RSA_P);sk.dp = RSA_DP;sk.dplen = sizeof(RSA_DP);sk.dq = RSA_DQ;sk.dqlen = sizeof(RSA_DQ);sk.iq = RSA_IQ;sk.iqlen = sizeof(RSA_IQ);uint32_t sign_result = br_rsa_i31_pkcs1_sign(BR_HASH_OID_SHA256,hash_value,sizeof(hash_value),&sk,signature);return sign_result != 0;
}int verify_signature(const unsigned char *signature, size_t sig_len, unsigned char *hash_out) {br_rsa_public_key pk;pk.n = RSA_N;pk.nlen = sizeof(RSA_N);pk.e = RSA_E;pk.elen = sizeof(RSA_E);uint32_t verify_result = br_rsa_i31_pkcs1_vrfy(signature,sig_len,BR_HASH_OID_SHA256,sizeof(hash_value),&pk,hash_out);if (!verify_result) {return 0;}return memcmp(hash_out, hash_value, sizeof(hash_value)) == 0;
}int main() {unsigned char signature[128] = {0};unsigned char verify_hash_out[32] = {0};printf("The original hash is: \n");for (int i = 0; i < 32; i++) {printf("%02x", hash_value[i]);}printf("\n");if (!sign_data(signature)) {printf("Failed sign!\n");return -1;}printf("Success sign, the signature is: \n");for (int i = 0; i < 128; i++) {printf("%02x", signature[i]);if((i+1)%32 ==0)printf("\n");}if (!verify_signature(signature, 128, verify_hash_out)) {printf("Failed verify!\n");return -1;}printf("Success verify, the hash out is: \n");for (int i = 0; i < 32; i++) {printf("%02x", verify_hash_out[i]);}printf("\n");return 0;
}
五、总结
本文详细介绍了基于BearSSL轻量级加密库实现RSA-PKCS#1数字签名与验签的全过程。BearSSL以其模块化设计和嵌入式友好特性,成为资源受限环境下安全通信的理想选择。文章重点剖析了i31位优化实现的RSA算法,该实现通过31位整数运算单元在32位CPU上达到性能与代码大小的最佳平衡。工程采用精简化架构,仅集成必要的i31运算模块,通过CMake构建系统实现高效管理。测试方案采用确定性测试向量,完整演示了从私钥签名到公钥验签的闭环流程,包含严格的返回值检查和内存比较验证。整个实现充分展现BearSSL的安全设计哲学:恒定时间算法防御旁路攻击、显式内存管理避免动态分配、最小化API降低误用风险。该方案特别适合物联网设备等嵌入式场景,为开发者提供了兼具安全性和效率的轻量级密码学实践范例,其模块化思想也可扩展至其他加密算法实现。