3、数字签名原理:非对称加密、哈希函数、签名与验签流程

数字签名这东西,说白了就是现实世界里签章、指纹的电子版。但它的核心价值在于——你没法抵赖,别人也没法伪造。我在做安全启动方案时,经常跟团队讲一句话:没有数字签名的固件,就像没贴封条的快递,谁都能往里塞东西。

这一节我们就把数字签名的三个核心构件拆开来看:非对称加密、哈希函数,以及它们怎么组合成签名与验签流程。

3.1 非对称加密:一对钥匙的哲学

非对称加密,也叫公钥密码体制。它有一对密钥:公钥和私钥。公钥可以公开,私钥必须保密。

我习惯用一个比喻来理解:
公钥就像一把挂锁,谁都能拿到,谁都能用它把箱子锁上。
私钥就是唯一能打开这把锁的钥匙,只有你自己有。

在数字签名场景里,我们用的是私钥签名,公钥验签。跟加密正好反过来——加密是公钥加密、私钥解密。

核心原则:

  • 私钥签名:只有持有私钥的人才能生成有效签名
  • 公钥验签:任何人都可以用公钥验证签名是否由对应私钥生成
  • 私钥一旦泄露,整个信任体系就崩塌了

常见的非对称算法有 RSA、ECDSA、Ed25519。我个人在启动链场景里更倾向 ECDSA 或 Ed25519,因为密钥长度更短,签名性能更好。RSA 虽然成熟,但密钥太长,在嵌入式环境里有点笨重。

3.2 哈希函数:数据的数字指纹

哈希函数,就是把任意长度的数据,压缩成一个固定长度的摘要。这个摘要就像数据的数字指纹。

一个好的哈希函数要满足几个特性:

  • 单向性:从摘要反推原始数据,几乎不可能
  • 抗碰撞性:很难找到两个不同的数据,产生相同的摘要
  • 雪崩效应:输入改一个比特,摘要就面目全非

我在项目中遇到过一个问题:有人直接用 SHA-1 做固件哈希。嗯,SHA-1 已经被证明存在碰撞攻击了。我当时的建议是——至少用 SHA-256,最好用 SHA-384 或 SHA-512。别为了省那点计算时间,把整个安全体系搭进去。

哈希算法 摘要长度 安全状态 推荐场景
SHA-1 160 bit 已不安全 不推荐使用
SHA-256 256 bit 安全 通用场景
SHA-384 384 bit 安全 高安全场景
SHA-512 512 bit 安全 极高安全场景

3.3 签名与验签流程:一张图讲清楚

下面这张图,是我每次培训必画的。它把整个流程拆成了两个阶段:签名阶段和验签阶段。

数字签名与验签流程 签名阶段(发送方) 原始数据 哈希函数 数据摘要 私钥加密 数字签名 验签阶段(接收方) 接收数据 哈希函数 摘要A 公钥解密 摘要B 比较摘要A == 摘要B

我的经验:签名阶段,私钥加密的是摘要,不是原始数据。这样做有两个好处:一是速度快(摘要很短),二是安全(私钥只处理摘要,不接触原始数据)。

3.4 签名流程详解

签名阶段,发送方要做三件事:

  1. 计算哈希:对原始数据做哈希运算,得到固定长度的摘要
  2. 私钥签名:用自己的私钥对摘要进行加密,生成数字签名
  3. 打包发送:把原始数据 + 数字签名一起发给接收方

你想想看,这里有个关键点——签名不是加密数据,而是加密数据的摘要。这样做的好处是,不管原始数据多大,签名计算量都只跟摘要长度有关。

3.5 验签流程详解

接收方收到数据后,做四步验证:

  1. 计算哈希:对收到的原始数据做同样的哈希运算,得到摘要A
  2. 公钥解密:用发送方的公钥解密数字签名,得到摘要B
  3. 比较摘要:比较摘要A和摘要B是否相等
  4. 判定结果:相等则签名有效,数据未被篡改;不等则签名无效

我曾经踩过的坑:有一次在验签时,公钥拿错了。结果签名验证一直失败,排查了半天才发现是密钥管理的问题。所以,公钥的完整性验证同样重要——公钥本身也要有可信的获取渠道,否则中间人攻击就能轻松替换公钥。

3.6 为什么哈希函数不可或缺?

有人可能会问:为什么不直接对原始数据做签名?

原因很简单:性能。非对称加密的计算开销很大,尤其是 RSA。如果直接对几兆字节的固件做签名,那速度慢得让人抓狂。哈希函数把数据压缩成固定长度的摘要,签名计算量就降下来了。

另一个原因是安全性。哈希函数的雪崩效应保证了——哪怕数据只改了一个比特,摘要也会完全改变。这样,任何篡改都会被验签阶段发现。

一句话总结:哈希函数负责压缩和防篡改,非对称加密负责身份认证和不可抵赖。两者结合,才是完整的数字签名。

3.7 代码示例:用 OpenSSL 演示签名与验签

下面我用 OpenSSL 命令演示一下完整的签名验签流程。你可以在自己的环境里跑一遍,感受一下。

# 1. 生成私钥(ECDSA,P-256曲线)
openssl ecparam -genkey -name prime256v1 -out private.pem

# 2. 从私钥导出公钥
openssl ec -in private.pem -pubout -out public.pem

# 3. 准备待签名的数据
echo "Hello, Secure Boot!" > data.txt

# 4. 签名:对数据做哈希,然后用私钥签名
openssl dgst -sha256 -sign private.pem -out signature.bin data.txt

# 5. 验签:用公钥验证签名
openssl dgst -sha256 -verify public.pem -signature signature.bin data.txt

# 验签成功会输出:Verified OK

你看,整个流程就这几步。但背后的原理,就是我们刚才讲的那些。

3.8 启动链中的签名应用

在安全启动场景里,数字签名用在每一个加载阶段:

  • BootROM 验签 Bootloader 1
  • Bootloader 1 验签 Bootloader 2
  • Bootloader 2 验签操作系统内核
  • 内核 验签关键驱动或模块

每一级都用自己的公钥去验上一级的签名。这就是信任链的根基。

我建议:在实际项目中,公钥最好硬件固化在芯片的 OTP(一次性可编程)区域。这样,攻击者即使拿到了芯片,也没法篡改公钥。我在一个 IoT 项目里就是这么做的,效果很好。