1. 项目概述
在嵌入式系统开发中,安全通信和数据保护是至关重要的需求。STM32作为广泛使用的微控制器系列,其安全功能的实现往往需要借助第三方加密库。Mbed TLS(前身为PolarSSL)是一个轻量级的SSL/TLS实现,专为嵌入式系统设计,提供了丰富的加密算法和安全协议支持。
本项目基于STM32F103ZET6平台,使用Keil MDK开发环境,将Mbed TLS 2.24.0移植到STM32上,并重点实现了ECDSA(椭圆曲线数字签名算法)功能。ECDSA相比传统RSA算法具有密钥短、安全性高的特点,特别适合资源受限的嵌入式设备。
2. 开发环境搭建
2.1 硬件准备
- 主控芯片:STM32F103ZET6(Cortex-M3内核,72MHz主频,64KB RAM,512KB Flash)
- 开发环境:Keil MDK-ARM v5
- 调试工具:J-Link或ST-Link仿真器
2.2 软件准备
-
获取Mbed TLS源码:
bash复制git clone -b mbedtls-2.24.0 https://github.com/ARMmbed/mbedtls.git -
关键目录说明:
include/:包含所有头文件library/:算法实现源码configs/:各种配置模板文件
-
工程配置:
- 在Keil中添加Mbed TLS源码到工程
- 设置正确的头文件包含路径
- 根据需求调整编译选项
3. ECDSA算法原理与实现
3.1 ECDSA算法简介
ECDSA是基于椭圆曲线密码学的数字签名算法,相比RSA具有以下优势:
- 更短的密钥长度:256位ECDSA密钥提供的安全性相当于3072位RSA密钥
- 更快的签名速度:在相同安全级别下,ECDSA签名速度比RSA快得多
- 更低的内存占用:适合资源受限的嵌入式设备
3.2 密钥长度对比
| 安全强度(bits) | RSA密钥长度(bits) | ECDSA密钥长度(bits) |
|---|---|---|
| 80 | 1024 | 160 |
| 112 | 2048 | 224 |
| 128 | 3072 | 256 |
3.3 ECDSA签名流程
-
密钥生成:
- 选择椭圆曲线参数(如secp256r1)
- 生成私钥d(随机数)
- 计算公钥Q = d×G(G为曲线基点)
-
签名生成:
- 计算消息哈希值e = Hash(m)
- 生成随机数k
- 计算曲线点(k×G)的x坐标r
- 计算s = k⁻¹(e + r×d) mod n
- 签名结果为(r, s)
-
签名验证:
- 计算消息哈希值e = Hash(m)
- 计算w = s⁻¹ mod n
- 计算u1 = e×w mod n, u2 = r×w mod n
- 计算曲线点(u1×G + u2×Q)的x坐标v
- 验证v ≡ r mod n
4. 移植与配置
4.1 内存配置调整
由于Mbed TLS需要较多内存,必须调整STM32的堆栈大小:
assembly复制Stack_Size EQU 0x00005000 ; 20KB栈空间
Heap_Size EQU 0x00002500 ; 9KB堆空间
注意:实际值应根据具体芯片型号和可用内存调整。STM32F103ZET6有64KB RAM,上述配置约占一半内存。
4.2 Mbed TLS配置
修改config-mini-tls1_1.h配置文件:
c复制#define MBEDTLS_AES_C
#define MBEDTLS_CTR_DRBG_C
#define MBEDTLS_ENTROPY_C
#define MBEDTLS_SHA256_C
#define MBEDTLS_MD_C
#define MBEDTLS_BIGNUM_C
#define MBEDTLS_ECP_C
#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
#define MBEDTLS_ECDSA_C
#define MBEDTLS_ASN1_PARSE_C
#define MBEDTLS_PK_PARSE_C
#define MBEDTLS_PK_C
#define MBEDTLS_OID_C
4.3 硬件随机数实现
STM32F103没有硬件随机数生成器,需要实现软件随机数函数:
c复制int mbedtls_hardware_poll(void *data, unsigned char *output, size_t len, size_t *olen) {
unsigned long randomValue = ((rand()*20) + 1000); // 简单随机数生成
((void) data);
*olen = 0;
if(len < sizeof(unsigned long)) return 0;
memcpy(output, &randomValue, sizeof(unsigned long));
*olen = sizeof(unsigned long);
return 0;
}
提示:生产环境应使用更安全的随机数生成方式,如结合ADC采样噪声等。
5. ECDSA实现与验证
5.1 密钥生成与签名
c复制mbedtls_ecdsa_context ctx;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
// 初始化上下文
mbedtls_ecdsa_init(&ctx);
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctr_drbg);
// 生成密钥对
mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
(const unsigned char *)"ecdsa", 5);
mbedtls_ecdsa_genkey(&ctx, MBEDTLS_ECP_DP_SECP256R1,
mbedtls_ctr_drbg_random, &ctr_drbg);
// 计算消息哈希
unsigned char hash[32];
mbedtls_sha256_ret(message, strlen(message), hash, 0);
// 生成签名
unsigned char sig[MBEDTLS_ECDSA_MAX_LEN];
size_t sig_len;
mbedtls_ecdsa_write_signature(&ctx, MBEDTLS_MD_SHA256, hash, sizeof(hash),
sig, &sig_len, mbedtls_ctr_drbg_random, &ctr_drbg);
5.2 签名验证
c复制// 验证签名
int ret = mbedtls_ecdsa_read_signature(&ctx, hash, sizeof(hash), sig, sig_len);
if(ret == 0) {
printf("Signature verification succeeded\n");
} else {
printf("Signature verification failed: -0x%04X\n", -ret);
}
6. 使用预置公钥验证
6.1 公钥解析
c复制const uint8_t public_key[] = {
"-----BEGIN PUBLIC KEY-----\n"
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvaJpE+eaJ4iVlurAR2xc1gZRDBsY\n"
"zRttienho/a5qGiPe54eip4MzJh5G80dh5PrTlRpHq6nRdmbElBZ9H0lEQ==\n"
"-----END PUBLIC KEY-----\n"
};
mbedtls_pk_context pk;
mbedtls_pk_init(&pk);
int ret = mbedtls_pk_parse_public_key(&pk, public_key, strlen(public_key)+1);
if(ret != 0) {
printf("Failed to parse public key: -0x%04X\n", -ret);
return;
}
6.2 验签实现
c复制mbedtls_ecp_keypair *ecp = mbedtls_pk_ec(pk);
ret = mbedtls_ecdsa_read_signature((mbedtls_ecdsa_context *)ecp,
hash_text, sizeof(hash_text),
signature, sizeof(signature));
if(ret == 0) {
printf("Verification OK\n");
} else {
printf("Verification failed: -0x%04X\n", -ret);
}
7. 常见问题与解决方案
7.1 堆栈溢出
现象:程序运行时崩溃,调试发现堆栈溢出。
解决方案:
- 增大堆栈大小(如前面所述)
- 优化代码减少局部变量使用
- 避免大数组作为局部变量
7.2 公钥解析失败
可能原因:
- PEM格式不正确(缺少头尾标记)
- 内存不足
- 配置选项未启用(如MBEDTLS_PK_PARSE_C)
排查步骤:
- 检查PEM格式是否正确
- 使用OpenSSL验证公钥有效性:
bash复制openssl ec -pubin -in public_key.pem -text -noout - 调试查看DER解码结果
7.3 签名验证失败
常见原因:
- 使用了错误的曲线类型(如secp256k1代替secp256r1)
- 哈希值不匹配
- 签名格式不正确
验证方法:
- 确保签名以0x30开头(ASN.1序列标识)
- 使用OpenSSL验证签名:
bash复制
openssl dgst -sha256 -verify public_key.pem -signature signature.bin message.txt
7.4 性能优化
ECDSA验签在STM32F103上较慢,可考虑以下优化:
- 使用硬件加速:STM32F4/F7/H7系列有硬件加密模块
- 优化大数运算:替换默认的MPI实现
- 降低曲线参数:使用secp192r1代替secp256r1(降低安全性)
- 预计算:对固定公钥预先计算部分参数
8. 实际应用建议
-
密钥管理:
- 私钥必须安全存储,建议使用STM32的Flash保护功能
- 考虑使用HSM(硬件安全模块)或SE(安全元件)存储密钥
-
随机数生成:
- 生产环境不应使用简单的伪随机数
- 可结合ADC采样、RTC抖动等熵源
-
性能考量:
- ECDSA签名验证在STM32F103上约需500ms
- 对性能敏感场景考虑使用ECC加速芯片
-
协议设计:
- 结合HMAC防止重放攻击
- ��加时间戳或序列号
- 考虑使用TLS协议而非裸ECDSA
9. 扩展功能实现
9.1 公钥导出
c复制unsigned char pub_key_buf[65]; // 未压缩公钥格式:0x04 + x + y
size_t olen;
mbedtls_ecp_point_write_binary(&ecp->grp, &ecp->Q, MBEDTLS_ECP_PF_UNCOMPRESSED,
&olen, pub_key_buf, sizeof(pub_key_buf));
9.2 PKCS#8格式密钥解析
c复制mbedtls_pk_parse_key(&pk, private_key, strlen(private_key)+1, NULL, 0);
9.3 证书验证
- 启用MBEDTLS_X509相关配置
- 解析证书链
- 验证签名和有效期
10. 移植心得与注意事项
- 配置裁剪:根据实际需求启用/禁用算法,减少代码体积
- 内存管理:嵌入式设备内存有限,需仔细规划使用
- 错误处理:Mbed TLS返回丰富的错误码,应妥善处理
- 调试技巧:
- 使用
mbedtls_debug_set_threshold()开启调试输出 - 逐步验证各组件功能
- 使用
- 安全考虑:
- 避免内存泄漏
- 防止时序攻击
- 确保随机数质量
通过本项目,我们成功将Mbed TLS移植到STM32平台,并实现了ECDSA签名验证功能。在实际应用中,还需要考虑更多安全因素和性能优化。希望本文能为嵌入式安全开发提供有价值的参考。
