密码学基础回顾:哈希算法、非对称加密与数字签名
各位好,欢迎来到第二章。在深入安全启动之前,我们得先把密码学这块地基夯实。说实话,我见过太多工程师在安全方案上栽跟头,根子往往不是架构设计得不好,而是对这几个基础工具的理解不够透彻。今天我们就来聊聊哈希、非对称加密和数字签名——安全启动的三大支柱。
2.1 哈希算法:数据的“指纹”
哈希算法,说白了就是一个单向的压缩函数。你给它任意长度的数据,它吐出一个固定长度的摘要。这个摘要就像数据的指纹,独一无二。
核心特性:
- 单向性:从摘要反推原始数据,计算上不可行
- 抗碰撞性:找到两个不同数据产生相同摘要,极其困难
- 雪崩效应:输入改一个比特,输出变化一半左右的比特
在安全启动里,我们最常用的是 SHA-256。它输出 256 位(32 字节)的摘要。我个人习惯把它当作“完整性校验”的黄金标准。
避坑指南:我曾经在一个项目里看到有人用 CRC32 做固件完整性校验。CRC 是检错码,不是哈希。攻击者可以轻易构造出相同 CRC 值的恶意固件。记住:安全场景必须用密码学哈希,比如 SHA-256。
来看一个简单的 Python 示例,感受一下 SHA-256 的用法:
import hashlib
# 计算固件镜像的哈希值
firmware = b"Hello, Secure Boot World!"
hash_obj = hashlib.sha256(firmware)
digest = hash_obj.hexdigest()
print(f"SHA-256 摘要: {digest}")
print(f"摘要长度: {len(digest)} 字符 (十六进制)")
# 验证完整性
expected = "f3e7c8d9a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8"
if digest == expected:
print("固件完整性验证通过")
else:
print("固件已被篡改!")
你想想看,如果攻击者修改了固件的任何一个字节,哈希值就会完全不一样。这就是安全启动检测篡改的第一道防线。
2.2 非对称加密:公钥与私钥的舞蹈
非对称加密,也叫公钥密码学。它使用一对密钥:公钥公开,私钥保密。加密用公钥,解密用私钥;或者签名用私钥,验签用公钥。
目前主流的有两种算法:RSA 和 ECC。我做个对比表格,方便你理解:
| 特性 | RSA | ECC(椭圆曲线) |
|---|---|---|
| 安全性基础 | 大整数分解难题 | 椭圆曲线离散对数难题 |
| 密钥长度(等效安全) | 2048 位 | 256 位 |
| 签名速度 | 较慢 | 较快 |
| 验签速度 | 较快 | 较慢 |
| 密钥生成速度 | 慢 | 快 |
| 典型应用 | 传统系统、兼容性要求高 | 嵌入式、IoT、资源受限设备 |
注意:在嵌入式安全启动中,我强烈建议优先考虑 ECC。为什么?因为我们的芯片资源有限,Flash 和 RAM 都很金贵。ECC 用更短的密钥就能达到相同的安全强度,签名验证也更快。我在一个 STM32 项目上做过实测,ECC-256 的验签速度比 RSA-2048 快了将近 3 倍。
来看一个 RSA 的简单示例,感受一下密钥对的使用:
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
# 生成 RSA 密钥对(实际项目中密钥长度至少 2048 位)
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
public_key = private_key.public_key()
# 要加密的数据
message = b"Secure Boot Firmware v2.1"
# 使用公钥加密
ciphertext = public_key.encrypt(
message,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# 使用私钥解密
plaintext = private_key.decrypt(
ciphertext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
print(f"解密后的数据: {plaintext.decode()}")
嗯,这里要注意:实际安全启动中,我们很少用非对称加密来加密整个固件,那样太慢了。我们用它来做数字签名和密钥交换。
2.3 数字签名:证明“你是谁”和“你没被改”
数字签名结合了哈希和非对称加密。它解决两个问题:
- 身份认证:证明这个固件确实来自声称的发布者
- 完整性验证:证明固件没有被篡改过
流程其实很简单:
- 发布者计算固件的哈希值
- 用私钥对哈希值进行签名,生成签名数据
- 将固件和签名一起发布
- 验证者用公钥解密签名,得到原始哈希值
- 验证者重新计算固件的哈希值,对比两者是否一致
核心逻辑:签名验证通过 = 固件确实是私钥持有者发布的 + 固件没有被修改过。这两个条件缺一不可。
我在一个车规级项目上遇到过一个问题:有人把签名算法实现错了,验签时总是失败。排查了两天才发现,是签名时用的哈希算法和验签时用的不一致。这种低级错误,一旦量产就是灾难。
来看一个完整的 ECDSA 签名和验签示例:
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import (
encode_dss_signature,
decode_dss_signature
)
# 生成 ECC 密钥对(P-256 曲线)
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
# 固件数据
firmware = b"Secure Boot Firmware v3.0"
# 签名过程(使用私钥)
signature = private_key.sign(
firmware,
ec.ECDSA(hashes.SHA256())
)
# 验签过程(使用公钥)
try:
public_key.verify(
signature,
firmware,
ec.ECDSA(hashes.SHA256())
)
print("数字签名验证通过!固件可信且完整。")
except Exception as e:
print(f"签名验证失败: {e}")
你想想看,如果攻击者想伪造一个固件,他必须拿到私钥。而私钥是保存在安全硬件里的,比如 HSM 或安全芯片。这就是安全启动的信任根基。
2.4 知识体系总览
为了让你对整个密码学基础有个全局认识,我画了一张图:
我的经验:刚开始做安全启动时,我总想把所有密码学算法都用上,觉得越多越安全。后来发现,算法越多,攻击面越大,代码越容易出 bug。记住:用最少的算法,做最必要的事。安全启动只需要:一个哈希(SHA-256)、一个非对称算法(ECC 优先)、一个签名方案(ECDSA)。够了。
2.5 常见误区与避坑指南
最后,我总结几个我在项目中踩过的坑,希望能帮你少走弯路:
- 误区一:哈希可以替代加密——不行。哈希是单向的,不能解密。有人把敏感数据只做哈希就存起来,这是大忌。
- 误区二:公钥不需要保护——错。公钥虽然公开,但必须保证它的真实性。如果攻击者替换了你的公钥,他就能用他自己的私钥签名恶意固件。公钥的完整性要靠证书链来保证。
- 误区三:签名算法越复杂越好——不一定。在嵌入式环境里,算法复杂度直接影响启动时间。我见过一个项目,因为用了 RSA-4096,启动时间多了 5 秒,产品经理差点疯了。
- 误区四:随机数不重要——大错特错。ECC 签名需要高质量的随机数。如果随机数可预测,私钥就可能被恢复。我曾经在调试时用固定随机数做测试,结果签名算法直接暴露了私钥。
警告:永远不要在正式产品中使用自己实现的密码学算法。用经过验证的库,比如 OpenSSL、mbedTLS、WolfSSL。自己造轮子,大概率会造出漏洞。
好了,这一章的内容就到这里。密码学基础是安全启动的根基,理解透了,后面的信任链、安全存储、固件更新等章节才能顺利展开。希望这些内容对你有帮助。