第四节:非对称加密(RSA/ECC)—— 密钥对与数字签名的实战落地
说实话,做嵌入式安全这些年,我接触最多的就是非对称加密。RSA 和 ECC,这两个名字你肯定不陌生。但真正在资源受限的 MCU 上把它们用好,坑可不少。今天我就把这两兄弟掰开揉碎,结合我踩过的坑,跟你聊聊。
4.1 RSA 算法原理 —— 大数分解的数学游戏
RSA 的核心,说白了就是「大数分解很难」。你想想看,两个大质数 p 和 q 相乘得到 n,这个很容易。但给你 n,让你反推出 p 和 q,那就难如登天了。RSA 的安全基石就在这儿。
具体流程我简单梳理一下:
- 选两个大质数 p 和 q,计算 n = p × q
- 计算欧拉函数 φ(n) = (p-1)(q-1)
- 选公钥指数 e,通常取 65537(为什么?因为它是质数,且二进制只有两个1,加密快)
- 算私钥 d,满足 e × d ≡ 1 (mod φ(n))
公钥就是 (n, e),私钥就是 (n, d)。
核心公式:
加密:c = m^e mod n
解密:m = c^d mod n
我在项目中遇到过一个问题:有次用 STM32F4 做 RSA-2048 解密,发现耗时超过 2 秒。嗯,这显然不行。后来发现是模幂运算没做优化。记住,嵌入式里千万别直接调大数库的原始接口,一定要用 CRT(中国剩余定理)加速,能把解密速度提升 4 倍左右。
4.2 ECC 椭圆曲线 —— 更小的密钥,更强的安全
ECC 跟 RSA 不同。它不依赖大数分解,而是基于椭圆曲线上的离散对数问题。你想想看,在曲线上给定点 G 和 kG,求 k 几乎不可能。这就是 ECC 的安全基础。
我个人习惯用 secp256r1 曲线,也就是 NIST P-256。为什么?因为它在安全性和性能之间平衡得最好。256 位的 ECC 密钥,安全强度相当于 3072 位的 RSA。在嵌入式设备里,这省下的存储空间和计算时间可不是一星半点。
我的建议:新项目优先选 ECC。除非你要兼容老设备,否则别碰 RSA。我在一个智能门锁项目里,把 RSA-2048 换成 ECC-256,密钥存储从 512 字节降到 64 字节,握手时间从 1.2 秒降到 0.3 秒。效果立竿见影。
ECC 的密钥对生成也很简单:
- 私钥 d:一个随机大整数
- 公钥 Q:Q = d × G(G 是曲线上的基点)
这里有个坑:随机数生成器一定要够「随机」。我曾经见过某款芯片的硬件随机数生成器在刚上电时输出全是 0xAA,导致所有设备的私钥都一样。嗯,那场面,你懂的。
4.3 密钥对生成 —— 嵌入式环境下的注意事项
在 PC 上生成密钥对很简单,openssl 一行命令搞定。但在嵌入式设备里,你得考虑几个问题:
| 考量点 | RSA | ECC |
|---|---|---|
| 生成时间 | 慢(找大质数很耗时) | 快(只需随机数 + 点乘) |
| 存储空间 | 大(2048位 = 256字节) | 小(256位 = 32字节) |
| 随机数要求 | 中等 | 高(私钥必须真随机) |
我建议在产线预生成密钥对,烧录到芯片的 OTP 区域。不要在设备首次启动时生成,因为那会儿熵池可能还没准备好。我曾经吃过这个亏,设备启动后生成的密钥对居然有规律可循,吓得我赶紧改了产线流程。
4.4 数字签名与验签 —— 谁签的?改没改?
数字签名解决两个问题:身份认证和完整性校验。说白了就是「谁签的」和「改没改」。
RSA 签名流程:
- 对消息 m 做哈希,得到 h = SHA256(m)
- 用私钥签名:s = h^d mod n
- 发送 (m, s)
验签流程:
- 收到 (m, s)
- 计算 h' = SHA256(m)
- 用公钥解密:h = s^e mod n
- 比较 h 和 h',一致则通过
ECC 签名(ECDSA)稍微复杂一点,但核心思想一样。区别在于 ECC 的签名结果是一对整数 (r, s),而不是一个大整数。
避坑指南:我曾经在验签时忘记检查 r 和 s 是否在有效范围内,结果被注入了一个无效签名,导致验签通过。嗯,这个漏洞在 CVE 里都有记录。记住:验签的第一步,永远先检查 r 和 s 是否在 [1, n-1] 范围内。
4.5 知识体系总览
下面这张图,是我自己梳理的非对称加密知识框架。你把它存下来,以后做方案选型时对照着看,思路会清晰很多。
4.6 实战代码片段 —— ECDSA 签名与验签
下面这段代码,是我在 STM32H7 上跑过的 ECDSA 签名示例。你注意看注释里的坑:
// 使用 mbedTLS 库进行 ECDSA 签名
#include <mbedtls/ecdsa.h>
#include <mbedtls/sha256.h>
int ecdsa_sign_example(uint8_t *msg, uint32_t msg_len,
uint8_t *priv_key, uint8_t *signature) {
mbedtls_ecdsa_context ctx;
mbedtls_ecdsa_init(&ctx);
// 加载曲线 secp256r1
mbedtls_ecp_group_load(&ctx.grp, MBEDTLS_ECP_DP_SECP256R1);
// 读取私钥(32字节)
mbedtls_mpi_read_binary(&ctx.d, priv_key, 32);
// 计算消息哈希
uint8_t hash[32];
mbedtls_sha256(msg, msg_len, hash, 0);
// 签名
mbedtls_mpi r, s;
mbedtls_mpi_init(&r);
mbedtls_mpi_init(&s);
// 注意:这里需要真随机数!
mbedtls_ecdsa_sign(&ctx.grp, &r, &s, &ctx.d,
hash, sizeof(hash),
mbedtls_ctr_drbg_random, &ctr_drbg);
// 输出签名 (r || s)
mbedtls_mpi_write_binary(&r, signature, 32);
mbedtls_mpi_write_binary(&s, signature + 32, 32);
mbedtls_ecdsa_free(&ctx);
return 0;
}
验签时的关键检查:
1. 检查 r 和 s 是否在 [1, n-1] 范围内
2. 检查签名长度是否为 64 字节(对于 P-256)
3. 使用公钥验签前,先验证公钥是否在曲线上
这三点缺一不可。我见过太多人只做第三步,结果被签名伪造攻击搞翻车。
好了,非对称加密这块儿就聊到这儿。记住一句话:RSA 是老兵,可靠但笨重;ECC 是新锐,灵活但需要细心。选哪个,看你项目的具体场景。但无论选哪个,私钥保护和随机数质量永远是第一位的。