密码学基础回顾:哈希、非对称加密与数字签名

做固件安全,绕不开密码学。很多工程师觉得这东西太理论,直接调库就完事了。但说实话,我在调试安全启动时踩过的坑,十有八九都是因为对底层原理理解不透。这一章我们快速过一遍三个核心概念:哈希算法、非对称加密、数字签名。不求面面俱到,只求够用、不翻车。

1. 哈希算法:固件的“指纹”

哈希算法,说白了就是把任意长度的数据,压缩成一个固定长度的摘要。这个摘要就像人的指纹——数据变了,指纹一定变。

我常用的哈希算法是 SHA-256。为什么是它?因为 SHA-1 已经被证明有碰撞风险,而 SHA-256 目前还是安全的。在固件签名场景里,我们通常先用 SHA-256 算出固件的哈希值,然后再对这个哈希值做签名。

SHA-256 的核心特性:

  • 单向性:从哈希值无法反推出原始数据。你想想看,如果固件哈希能被逆向,那签名就形同虚设了。
  • 抗碰撞性:极难找到两个不同的输入,产生相同的哈希值。我曾经在项目中遇到过有人试图用伪造固件碰撞哈希,好在 SHA-256 足够强。
  • 雪崩效应:输入哪怕只改一个比特,输出的哈希值也会天翻地覆。

来看一个简单的 Python 示例,计算固件文件的 SHA-256:

import hashlib

def calculate_sha256(file_path):
    sha256_hash = hashlib.sha256()
    with open(file_path, "rb") as f:
        # 分块读取,避免大固件撑爆内存
        for byte_block in iter(lambda: f.read(4096), b""):
            sha256_hash.update(byte_block)
    return sha256_hash.hexdigest()

# 使用示例
firmware_hash = calculate_sha256("firmware.bin")
print(f"SHA-256: {firmware_hash}")

我的习惯:在固件编译脚本里自动计算哈希,并把这个哈希值写入固件头的保留字段。这样启动时 BootROM 可以快速校验固件完整性,不用每次都重新算一遍。

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

非对称加密,就是有一对密钥:公钥公开,私钥保密。用公钥加密的数据,只能用私钥解密;反过来,用私钥签名的数据,只能用公钥验证。

在固件签名场景里,我们主要用两种算法:RSA 和 ECC。我个人更倾向于 ECC,因为它在同等安全强度下,密钥更短、计算更快。但很多老旧的 BootROM 只支持 RSA,所以选型时得看硬件支持情况。

特性 RSA ECC
密钥长度(安全等价) 2048 位 256 位
签名速度 较慢 较快
验签速度 较快 较慢
硬件支持 广泛 较新芯片支持
典型应用 传统安全启动 现代 IoT 设备

避坑指南:我曾经在一个项目里选了 ECC P-256,结果发现 BootROM 只支持 RSA 2048。最后不得不改方案,重新生成密钥对,折腾了两周。所以,做安全启动前,一定要先确认硬件密码学加速器支持哪些算法。

3. 数字签名:给固件盖上“防伪章”

数字签名,本质上就是用私钥对固件的哈希值进行加密。验证时,用公钥解密签名,得到原始的哈希值,再和当前固件的哈希值对比。如果一致,说明固件没被篡改,而且确实来自持有私钥的合法发布者。

为什么会这样?因为私钥只有发布者才有,别人伪造的签名,用公钥解密出来一定是乱码。

完整的签名流程如下:

  1. 计算哈希:对固件二进制做 SHA-256,得到 32 字节的摘要。
  2. 签名:用私钥对哈希值进行加密,生成签名数据。
  3. 打包:将签名数据附加到固件末尾,或者写入固件头的签名区。
  4. 分发:将固件和签名一起发布。

验证流程正好相反:

  1. 提取签名:从固件包中取出签名数据。
  2. 计算哈希:对固件本体重新计算 SHA-256。
  3. 验签:用公钥解密签名,得到原始哈希,与步骤 2 的哈希对比。
  4. 判定:一致则通过,不一致则拒绝启动。

核心要点:数字签名不是加密固件本身,而是加密固件的哈希值。这样做的好处是签名数据很小(RSA 2048 只有 256 字节),验签速度快,适合在资源受限的 BootROM 中执行。

下面是一个用 Python 演示签名和验签的示例:

from cryptography.hazmat.primitives import hashes, asymmetric
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives import serialization

# 生成 RSA 密钥对(实际生产中应安全保存私钥)
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
)
public_key = private_key.public_key()

# 模拟固件数据
firmware_data = b"This is my firmware binary content."

# 签名过程
signature = private_key.sign(
    firmware_data,
    padding.PSS(
        mgf=padding.MGF1(hashes.SHA256()),
        salt_length=padding.PSS.MAX_LENGTH
    ),
    hashes.SHA256()
)
print(f"签名长度: {len(signature)} 字节")

# 验签过程
try:
    public_key.verify(
        signature,
        firmware_data,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    print("验签通过:固件未被篡改")
except Exception as e:
    print(f"验签失败:{e}")

我的习惯:在生产环境中,我会把公钥烧录到芯片的一次性可编程(OTP)区域。这样即使固件被破解,攻击者也改不了公钥,无法用自己的私钥签发恶意固件。

4. 知识体系总览

下面这张图把本章的核心逻辑串起来了。你可以看到哈希、非对称加密、数字签名三者如何配合,最终构成安全启动的密码学基础。

固件签名密码学基础框架 固件二进制 SHA-256 哈希 私钥(保密) 数字签名 公钥(公开) 验签对比 通过 / 拒绝启动 核心逻辑 私钥签名 → 公钥验签 哈希保证完整性

嗯,到这里密码学基础就回顾完了。哈希、非对称加密、数字签名,这三个概念是固件安全的基石。后面的章节我们会基于这些知识,一步步搭建完整的安全启动链路。记住:理论可以简单,但实现必须严谨——我在生产环境里见过太多因为密钥管理不当导致整个安全体系崩塌的案例了。