密码学基础:哈希算法、非对称加密、数字签名原理

好,咱们进入正题。这一章是后面所有安全机制的地基。你想想看,VxWorks 安全启动也好,固件签名也罢,底层依赖的都是密码学。我见过不少工程师,上来就调 API,结果签名验证失败,连问题出在哪都不知道。说白了,就是基础没打牢。

这一章,我会把哈希、非对称加密、数字签名这三个核心概念讲透。不扯太深的理论,重点是你做嵌入式开发时,到底该怎么理解它们。

1. 哈希算法:数据的“指纹”

哈希算法,你可以把它想象成给数据按个手印。不管你的固件是 1KB 还是 1GB,经过哈希运算后,都会得到一个固定长度的输出,比如 SHA-256 就是 256 位。

它有这几个关键特性:

  • 单向性:从哈希值反推原始数据,理论上不可能。嗯,这里要注意,不是“很难”,是“不可能”。
  • 抗碰撞性:找到两个不同的输入,让它们的哈希值相同,几乎不可能。
  • 雪崩效应:输入改一个比特,输出结果会大变样。

重要:在 VxWorks 安全启动中,哈希算法用来校验固件的完整性。启动时,计算当前固件的哈希值,跟出厂时记录的哈希值对比。不一样?说明固件被篡改了。

我个人习惯用 SHA-256。SHA-1 已经被证明有碰撞风险了,千万别再用。我在一个老项目里就踩过这个坑,客户要求升级安全等级,结果发现 bootloader 里还硬编码着 SHA-1 的校验逻辑。改起来那叫一个痛苦。

经验之谈:做固件升级时,建议先算哈希,再比对。不要等整个固件刷完了才发现校验失败。我曾经见过一个案例,升级到 99% 时报错,设备变砖。就是因为没做前置校验。

2. 非对称加密:公钥与私钥

非对称加密,说白了就是两把钥匙。一把公开(公钥),一把私藏(私钥)。公钥加密的数据,只有私钥能解开。反过来,私钥加密的数据,公钥也能验证。

这和对称加密完全不同。对称加密就像一把钥匙开一把锁,加密解密用同一个密钥。问题来了——你怎么把密钥安全地传给对方?在嵌入式设备里,这几乎是个死循环。

非对称加密就解决了这个问题。你想想看,设备出厂时,我们只烧录公钥。私钥留在安全的签名服务器上。这样即使设备被破解,攻击者也拿不到私钥。

特性 对称加密 非对称加密
密钥数量 1 个 2 个(公钥 + 私钥)
速度 快(适合大数据) 慢(适合小数据)
密钥分发 困难 容易
典型算法 AES RSA, ECC

在 VxWorks 里,我建议用 ECC(椭圆曲线密码学)。为什么?同样的安全强度下,ECC 的密钥更短,计算更快。对于嵌入式这种资源受限的环境,ECC 比 RSA 友好得多。我记得有一次做性能评估,RSA-2048 签名验证要花 200 多毫秒,换成 ECC-256 后,直接降到 30 毫秒以内。

警告:千万不要把私钥放在固件里!我见过有人把私钥硬编码在源码中,然后上传到 Git 仓库。这等于把家门钥匙贴在门上。私钥必须离线保存,使用硬件安全模块(HSM)管理。

3. 数字签名:签名与验签

数字签名,其实就是哈希算法和非对称加密的结合体。流程是这样的:

  1. 签名方:对固件算哈希,然后用私钥加密这个哈希值。加密后的结果就是“签名”。
  2. 验证方:用公钥解密签名,得到原始的哈希值。再自己算一遍固件的哈希。两个哈希一致,说明固件没被改,而且确实是私钥持有者签发的。

你可能会问:为什么不直接加密整个固件?因为非对称加密太慢了。加密一个 256 位的哈希值,比加密几 MB 的固件快得多。这就是所谓的“先哈希,后签名”。

核心逻辑:数字签名保证了两个东西——完整性和真实性。完整性靠哈希,真实性靠非对称加密。缺一不可。

在 VxWorks 安全启动中,bootloader 会做验签。它用烧录在 OTP(一次性可编程存储器)里的公钥,去验证下一级固件的签名。验证通过,才跳转执行。这就是“信任链”的起点。

我曾经调试过一个启动失败的问题。现象是 bootloader 验签总是不通过。查了两天,最后发现是签名时用的哈希算法和验签时用的不一致。签名端用了 SHA-256,验签端代码里写死了 SHA-1。这种低级错误,一旦遇到,真的很让人抓狂。

避坑指南:签名和验签的算法参数必须完全一致。包括哈希算法、填充方式、密钥长度。建议在代码里用宏定义统一管理,不要到处硬编码。

4. 在 VxWorks 中的实际应用

好了,理论讲完了。咱们看看在 VxWorks 里具体怎么用。

VxWorks 提供了 libssllibcrypto 库,支持 OpenSSL 接口。你可以直接调用 EVP_DigestSign*EVP_DigestVerify* 系列函数。

一个典型的签名流程:

// 初始化签名上下文
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, pkey);

// 更新数据(可以分多次调用)
EVP_DigestSignUpdate(ctx, firmware_data, firmware_len);

// 生成签名
size_t sig_len;
EVP_DigestSignFinal(ctx, signature, &sig_len);

验签流程类似,只是把 Sign 换成 Verify

EVP_MD_CTX *ctx = EVP_MD_CTX_new();
EVP_DigestVerifyInit(ctx, NULL, EVP_sha256(), NULL, pkey);

EVP_DigestVerifyUpdate(ctx, firmware_data, firmware_len);

// 验证签名
int ret = EVP_DigestVerifyFinal(ctx, signature, sig_len);
if (ret == 1) {
    // 验证通过
} else {
    // 验证失败
}

嗯,这里要注意一点。在 VxWorks 的 bootloader 阶段,内存和堆栈都很有限。你可能会遇到 EVP_MD_CTX_new 分配失败的情况。我建议在编译时开启 VX_SSL 组件,并合理配置堆大小。

另外,公钥的存储位置也很关键。在安全启动场景下,公钥通常放在 OTP 或者 eFuse 里。这些区域是一次性编程的,烧进去就改不了。如果你只是做固件升级校验,公钥也可以放在 flash 的只读分区,但安全性会低一些。

总结一下:哈希算法是基础,非对称加密是核心,数字签名是应用。这三者环环相扣,构成了 VxWorks 安全启动的基石。下一章,我们会把这些知识串起来,讲具体的签名和验签流程。

好,这一章就到这里。有什么问题,欢迎交流。