4. 数字签名原理:签名生成、验证与哈希的关系

数字签名这东西,说白了就是固件在数字世界里的「手写签名」。你想想看,一份合同上签了名,就能证明是你签的,别人改不了,你也赖不掉。固件签名干的也是同样的事——证明固件来自合法发布者,并且没有被篡改过。

我在做嵌入式安全这行之前,总觉得签名是个很「重」的操作。后来踩过几次坑才明白,理解签名原理,是做好固件安全的第一道门槛。咱们今天就把签名生成、签名验证,还有它跟哈希的关系,掰开揉碎了讲清楚。

4.1 签名生成过程

签名生成,发生在固件发布者这一侧。也就是你编译好固件之后,准备发布之前,要做的那一步。

整个过程可以拆成三步:

  1. 计算固件哈希:对固件二进制文件做一次哈希运算,得到一个固定长度的摘要。
  2. 用私钥加密哈希:发布者用自己的私钥,对这个哈希值进行加密操作(实际是签名算法,不是简单加密)。
  3. 打包固件+签名:把原始固件和签名值打包在一起,形成可发布的固件包。

核心要点:签名不是对整个固件做加密,而是对固件的「指纹」(哈希值)做签名。这样做的好处是效率高——固件可能几十兆,哈希只有几十字节。

我习惯用一个简单的命令行流程来演示。假设你用的是 OpenSSL 工具链:

# 第一步:计算固件哈希
openssl dgst -sha256 firmware.bin | cut -d' ' -f2 > firmware.hash

# 第二步:用私钥签名哈希
openssl pkeyutl -sign \
    -inkey private_key.pem \
    -in firmware.hash \
    -out firmware.sig

# 第三步:打包(实际项目中会用自定义打包工具)
cat firmware.bin firmware.sig > firmware_package.bin

嗯,这里要注意——实际产品中很少直接用 OpenSSL 命令行来做签名。一般都是集成到 CI/CD 流水线里,用 SDK 或者专用签名工具。但原理是一样的。

我的经验:签名时一定要记录签名所用的算法和密钥版本。我曾经接手过一个项目,固件包里有签名但没注明算法版本,结果验证端猜了半天才试出来。后来我强制团队在固件头里加一个「签名算法ID」字段,再也没出过这种问题。

4.2 签名验证过程

签名验证,发生在设备端。设备收到固件包后,要确认这个固件确实是合法的发布者签发的,而且没有被篡改过。

验证流程也是三步:

  1. 拆包:从固件包中分离出原始固件和签名值。
  2. 计算固件哈希:对原始固件做同样的哈希运算,得到摘要A。
  3. 用公钥解密签名:用发布者的公钥解密签名值,得到摘要B。如果A == B,验证通过。

重要提醒:设备端必须安全存储公钥。如果公钥被替换,攻击者就能用自己的私钥签名恶意固件。我见过一个 IoT 设备,公钥直接明文存在 Flash 里,攻击者通过串口读出来就替换了——那签名等于白做。

验证的代码逻辑大概长这样:

// 伪代码:设备端签名验证
bool verify_firmware(uint8_t* firmware, uint32_t fw_len,
                     uint8_t* signature, uint32_t sig_len,
                     uint8_t* public_key) {
    // 1. 计算固件哈希
    uint8_t hash[32];
    sha256(firmware, fw_len, hash);
    
    // 2. 用公钥验证签名
    // 内部会解密签名得到原始哈希,然后对比
    return ecdsa_verify(public_key, hash, signature);
}

你可能会问:为什么验证时不是「解密签名得到哈希,再对比」这么简单?其实底层确实是这么做的,但签名算法(比如 ECDSA)的验证过程还包含一些数学校验,防止伪造签名。这里咱们不深究数学细节,记住「哈希对比」这个核心逻辑就够了。

4.3 签名与哈希的关系

哈希是签名的「地基」。没有哈希,签名就没法高效工作。我画了一张图,帮你理清它们的关系:

签名与哈希的关系 签名生成(发布者) 固件二进制 哈希运算 哈希值(摘要) 私钥签名 签名验证(设备端) 固件二进制 哈希运算 哈希值A 公钥验证 哈希值A == 哈希值B? 相等则验证通过 为什么用哈希? 1. 效率:固件可能几MB,哈希只有32字节(SHA-256) 2. 安全性:哈希是单向函数,无法从哈希反推固件内容 3. 唯一性:不同固件几乎不可能产生相同哈希(抗碰撞)

从图上你能看到,签名生成和验证都依赖哈希运算。哈希在这里扮演了「桥梁」的角色——它把任意大小的固件,映射成一个固定长度的摘要,然后签名算法只需要处理这个摘要就行。

哈希算法有几个关键特性,决定了它适合做签名的基础:

特性 说明 对签名的意义
单向性 从哈希值无法反推出原始数据 签名不会泄露固件内容
抗碰撞性 很难找到两个不同数据有相同哈希 防止伪造固件碰撞签名
固定长度 无论输入多大,输出长度固定 签名值大小固定,便于处理
雪崩效应 输入微小变化,哈希完全改变 任何篡改都会被检测到

避坑指南:我曾经在一个项目里用了 MD5 做签名哈希,当时觉得「反正签名本身有加密保护,哈希弱一点没关系」。结果被安全审计直接打回——MD5 的抗碰撞性已经被攻破,攻击者可以构造两个不同固件但 MD5 相同的碰撞。后来全部换成 SHA-256,再也没敢偷懒。

最后总结一下签名与哈希的关系,就三句话:

  • 哈希是签名的「减负器」:把大固件变成小摘要,签名算法只处理摘要。
  • 签名是哈希的「保护罩」:用私钥保护哈希不被篡改,同时证明哈希的来源。
  • 两者缺一不可:没有哈希,签名效率太低;没有签名,哈希可以被随意替换。