2. 密码学基础(上):哈希函数、数字签名与PKI

各位同学,欢迎来到密码学基础的上半部分。说实话,这部分内容看着有点枯燥,一堆数学公式和算法名字。但我要告诉你,这是整个安全启动的基石。你后面遇到的每一个安全机制,都离不开今天讲的这三个东西。

我个人习惯把这三者比作一个「信任三角」:哈希函数负责「验明正身」,数字签名负责「盖章确认」,PKI 负责「发身份证」。咱们一个一个来拆解。

2.1 哈希函数:数据的「指纹」

哈希函数,说白了就是一个「压缩器」。你给它任意长度的数据,它吐出一个固定长度的摘要。这个摘要,就是数据的「指纹」。

为什么需要它?你想想看,一个固件可能有几十兆字节。你要验证它的完整性,总不能每次都把整个固件传一遍吧?太慢了。我们只需要验证它的哈希值就行。

SHA-256 是目前最常用的哈希算法。它输出 256 位,也就是 32 个字节。我当年做第一个安全启动项目时,用的就是它。

哈希函数的三个核心特性:
  • 抗原像性:给定一个哈希值,你几乎不可能反推出原始数据。这叫「单向性」。
  • 抗第二原像性:给定一个数据和它的哈希值,你找不到另一个数据产生同样的哈希值。
  • 抗碰撞性:你找不到任意两个不同的数据,它们的哈希值相同。

嗯,这里要注意第三点。理论上,碰撞是存在的,因为输入空间无限,输出空间有限。但 SHA-256 的碰撞概率低到可以忽略不计。我见过有人担心这个,其实大可不必——除非你面对的是国家级攻击者。

来看一个简单的 Python 示例,计算字符串的 SHA-256 哈希值:

import hashlib

data = b"Hello, Secure Boot!"
hash_obj = hashlib.sha256(data)
digest = hash_obj.hexdigest()

print(f"原始数据: {data}")
print(f"SHA-256 哈希: {digest}")
print(f"哈希长度: {len(digest)} 个十六进制字符")

输出结果会是一个 64 个字符的十六进制串。你改一个字母试试?整个哈希值会变得面目全非。这就是所谓的「雪崩效应」。

我的经验:在实际项目中,不要只依赖哈希值做完整性校验。我曾经遇到过一个案例,攻击者替换了固件,但保留了原固件的哈希值。所以,哈希值本身也需要被保护——这就是数字签名登场的地方。

2.2 数字签名:用私钥「盖章」

哈希函数只能保证数据没被篡改,但它不能证明这个数据是谁发的。你想想看,如果攻击者把固件和它的哈希值一起替换了,你怎么知道哪个是真的?

数字签名解决了这个问题。它用一对密钥:私钥签名,公钥验签。私钥只有你一个人有,公钥可以公开。

目前主流的有两种:RSA 和 ECDSA。

特性 RSA ECDSA
安全性基础 大整数分解 椭圆曲线离散对数
密钥长度 2048/4096 位 256/384 位
签名速度 较慢 较快
验签速度 较快 较慢
典型应用 传统 PKI、代码签名 嵌入式、IoT、区块链

我个人更倾向于在嵌入式场景中使用 ECDSA。为什么?因为密钥短,计算快,省电。我做过一个项目,MCU 只有 64KB 的 Flash,跑 RSA-2048 签名验证要好几秒,换成 ECDSA-P256 后,几百毫秒就搞定了。

数字签名的流程是这样的:

  1. 对原始数据计算哈希值(比如 SHA-256)。
  2. 用私钥对这个哈希值进行加密,得到签名。
  3. 把原始数据和签名一起发送出去。
  4. 接收方用公钥解密签名,得到哈希值 A。
  5. 接收方自己对原始数据计算哈希值 B。
  6. 比较 A 和 B,一致则签名有效。

你看,这里哈希函数和数字签名是配合使用的。哈希函数负责压缩数据,数字签名负责保护哈希值。

避坑指南:我曾经犯过一个错误——在签名前没有对哈希值做 padding。RSA 签名需要特定的填充方案(如 PKCS#1 v1.5 或 PSS),否则会有安全漏洞。ECDSA 虽然不需要 padding,但要注意随机数的质量。如果随机数可预测,私钥就可能被恢复出来。这不是开玩笑,索尼的 PS3 就栽在这个坑里。

2.3 公钥基础设施:信任的「身份证系统」

好,现在你有了一对密钥。你把公钥发给别人,别人用它来验证你的签名。但问题来了:你怎么证明这个公钥真的是你的?

这就是 PKI 要解决的问题。PKI 是一个完整的信任体系,核心是证书颁发机构(CA)。CA 就像公安局,它给你发一张「数字身份证」——也就是数字证书。

数字证书里包含什么?

  • 持有者的公钥
  • 持有者的身份信息(比如公司名、域名)
  • 证书的有效期
  • CA 的数字签名
  • 证书序列号等元数据

CA 用自己的私钥给你的证书签名。别人拿到你的证书后,用 CA 的公钥验证签名,就知道这个证书是真的。这就形成了一个信任链:

根 CA → 中间 CA → 终端证书

在安全启动中,这个信任链被固化在硬件里。芯片出厂时,根 CA 的公钥被烧录在一次性可编程存储器(OTP)中,不可更改。然后,每一级固件都由上一级的私钥签名,芯片用上一级的公钥验证。

信任链的典型结构:
  1. 根信任:芯片 ROM 中的根公钥(硬件不可变)
  2. 一级 Bootloader:由根私钥签名,验证后加载
  3. 二级 Bootloader:由一级私钥签名,验证后加载
  4. 操作系统/应用:由二级私钥签名,验证后加载

每一级都验证下一级的签名,环环相扣。只要根信任不被攻破,整个链条就是安全的。

我参与过一个车规级项目,客户要求支持证书吊销。什么意思?如果某个私钥泄露了,CA 可以发布一个证书吊销列表(CRL),告诉所有设备「这个证书不再可信」。设备在验证签名时,需要先检查 CRL。嗯,这增加了复杂度,但安全性提升了一个档次。

我的建议:在设计 PKI 体系时,不要把根 CA 的私钥放在生产环境中。应该用离线机,放在保险柜里。每次签名都要走审批流程。我见过一个团队,为了图方便,把根私钥放在 CI/CD 服务器上——这简直是灾难。

2.4 三者如何协同工作?

咱们来捋一捋。在安全启动中,这三个东西是这样配合的:

  1. 哈希函数:对固件镜像计算摘要,确保完整性。
  2. 数字签名:用私钥对摘要签名,确保真实性和不可否认性。
  3. PKI:通过证书链分发公钥,确保公钥的可信性。

说白了,哈希函数是「锁」,数字签名是「钥匙」,PKI 是「钥匙的身份证」。三者缺一不可。

最后,留个思考题给你:如果攻击者物理接触了设备,能不能绕过这个信任链?比如,直接读取 OTP 中的根公钥,然后伪造一个签名?

答案是不能。因为私钥不在设备里,攻击者拿不到。但有一种攻击叫「回滚攻击」——攻击者把固件降级到有漏洞的旧版本。怎么防?嗯,这就要用到「抗回滚计数器」了,那是后面章节的内容。

好,今天就到这里。下一节我们讲对称加密和密钥协商,那是另一个有趣的话题。