RPM 的 Payload SHA256 Digest 与 YUM 校验原理深度解析

从文件格式到事务校验,完整还原 RPM 的安全机制
📌 疑难排查背景

关于文章开头提到的具体错误场景(rust-1.91.1-9.zncgsl6.aarch64 包的 Payload SHA256 digest 校验失败),其常见的排查思路与解决方案如下:通常优先 mock --scrub=all 彻底清理构建环境,该操作可解决绝大多数的元数据不一致与缓存损坏问题,再通过更换镜像源和验证网络完整性进行针对性处理。

一、从文件格式出发:深入理解 Payload Digest 的技术本质

1.1 RPM 文件的四段式结构

RPM 包由四个相互独立的逻辑段按顺序拼接而成:

段名称 作用
Lead 文件标识与兼容性魔数,固定 96 字节,以 0xED 0xAB 0xEE 0xDB 魔数开头
Signature 签名与摘要集合,零填充至 8 字节倍数
Header 包元数据(名称、版本、依赖、文件列表等),采用 Tag-Value 结构
Payload 被压缩的文件归档,通常为 cpio 格式,经 gzip/xz/zstd 等压缩

理解这个四段式布局是后续所有内容的基础------摘要与签名分散存储在 Signature 段和 Header 段中,而非单一位置。

1.2 核心概念:Payload Digest 的精确定义

Payload Digest 是对 RPM 包中 Payload 层的压缩数据(即压缩后的 cpio 归档)进行哈希计算后得到的校验值。

RPM 的文档和规范给出了一个清晰的表格来描述所有摘要与签名的分布:

标签类型 版本引入 算法 存储位置 校验范围
MD5(SIGMD5) 3.0 MD5 Signature 段 整个包(HP)
SHA1(SHA1HEADER) 4.0 SHA1 Signature 段 Header 段
PAYLOADDIGEST 4.14 SHA256 Header 段 压缩后的 Payload
PAYLOADDIGESTALT 4.16 SHA256 Header 段 解压后的 Payload
FILEDIGESTS 4.6 SHA256 Header 段 Payload 内单个文件
PAYLOADSHA256 4.14 SHA256 Header 段 压缩后的 Payload
PAYLOADSHA512 6.0 SHA512 Header 段 压缩后的 Payload

RPM 的官方文档将这种关系图示为:

S = Signature header - H = Main header - P = Payload - F = Files in the payload (uncompressed) - c = compressed content

核心结论:RPM 维护了多个层次的摘要,形成了一套纵深防御体系:

复制代码
RPM 包 → Signature 段 → 验证整个包完整性(传统 MD5)
       → Header 段 → 验证 Header 完整性(SHA1HEADER / SHA256HEADER)
                  → 验证 Payload 压缩数据(PAYLOADDIGEST / PAYLOADSHA256)
                  → 验证 Payload 解压后数据(PAYLOADDIGESTALT)
                  → 验证 Payload 内单个文件(FILEDIGESTS)

1.3 演进中的冲突:PAYLOADDIGEST 的硬编码问题与 PAYLOADSHA256 的引入

一个重要的技术细节是:现有版本的 RPM 中,PAYLOADDIGEST 标签的行为被硬编码 ,无法以可扩展的方式支持新算法。为了给新算法腾出空间,RPM 社区决定将 PAYLOADDIGEST 重命名为 PAYLOADSHA256

虽然这种重命名保持向下兼容,但在某些场景下(主要是那些直接查询标签而不通过 RPM API 的工具)可能会造成解析上的问题------这是理解为什么某些老旧工具在处理新版 RPM 包时可能出现奇怪行为的关键背景。RPM 6.0 版本将提供更完善的解决方案,包括 PAYLOADSHA512PAYLOADSHA3_256 等新摘要支持。

1.4 Payload Digest 与 GPG 签名的本质区别

两者在 RPM 安全体系中各司其职:

维度 Payload Digest GPG 签名
核心目的 完整性------数据没有被改过 真实性------数据是谁签发的
算法类型 哈希算法(SHA256、SHA512) 非对称加密(RSA、DSA、EdDSA)
能否被篡改且不被发现 修改数据 → 摘要不匹配 → 必被发现 无私钥 → 签名无效 → 必被发现

两者必须同时通过,包才算安全可信。

二、深入理解 sha256sum package.rpm 与 Payload Digest 的本质差异

2.1 计算范围完全不同------这就是差异的根源

对比项 sha256sum package.rpm RPM Payload Digest
计算对象 整个 .rpm 文件(Lead + Signature + Header + Payload) 仅 Payload 段的压缩数据
包含哪些 文件头 + 签名段 + 元数据 + 压缩的 cpio 归档 仅压缩的 cpio 归档
能否验证 Header 完整性 能(因为整个文件被纳入了计算) 不能(Header 由 SHA256HEADER 单独验证)
典型用途 验证整个文件完整性、检查磁盘 I/O 损坏 专注于验证包内容数据本身的正确性

2.2 为什么两者计算出的哈希值不同

用一张表格来直观理解两者的涵盖范围差异:

RPM 的一部分 sha256sum package.rpm 会计算吗 Payload Digest 会计算吗
Lead(96 字节魔数和历史信息) ✅ 会 ❌ 不会
Signature 段(签名、摘要集合) ✅ 会 ❌ 不会
Header 段(包名、版本、依赖列表等) ✅ 会 ❌ 不会
Payload(压缩后的 cpio 归档) ✅ 会 仅此部分

正是这种涵盖范围的差异,直接导致了两者的计算结果完全不同。

2.3 技术深究:为何摘要要作用在"压缩后"而非"解压前"

RPM 选择对压缩后的 Payload做摘要,而不是先做摘要再压缩,有着深层工程考量。

社区开发者 Jeff Johnson 在 2017 年的邮件列表讨论中对此给出了清晰的解释:

"Short answer: digesting compressed payload instead of compressing the digested payload is expedient.":对压缩后的 Payload 做摘要,比对摘要结果做压缩更高效。)

关键原因

  1. 流式处理需求:RPM 需要支持在没有大量内存开销的情况下流式验证和提取;
  2. 降本增效:先压缩再做摘要,允许在解析 Payload 的真实内容(如 magic 头部)之前,就轻松设置摘要/压缩参数;
  3. 应用适配:那些不使用完整 RPM API 而直接操作 RPM 文件格式的第三方工具,可以显著降低实现复杂度。

如果没有这个机制,Verifying 一个 4GB 的大包时,可能需要在内存中完整解压全部内容(并重新压缩)后才能通过校验,这在内存受限的嵌入式系统中是不可接受的。

2.4 实践案例:提取 RPM Payload 并验证

使用 rpm2cpio 提取 Payload 内容:

bash 复制代码
# 将 RPM 包中的 CPIO 归档提取到当前目录
rpm2cpio package.rpm | cpio -idmv

# 列出 RPM 包中的文件(不实际提取)
rpm2cpio package.rpm | cpio -it | less

rpm2cpio 只提取 Payload 中的 CPIO 归档部分 ,不包括 Lead、Signature 和 Header 段。提取后对得到的文件进行 sha256sum 计算,然后与 package.rpmsha256sum 对比,两者一定不同,因为前者只包含文件数据,后者包含整个包。

核心结论sha256sum package.rpm 验证的是"文件传输/存储过程中是否损坏",而 RPM 的 Payload Digest 验证的是"这段压缩数据(Payload)是否与签名时一致"。两者服务于不同的安全目的,不能互相替代。

三、YUM/DNF 的事务校验机制与完整流程

3.1 事务的原子性保证

YUM/DNF 在设计上要求对一组软件包操作保证 "原子性" ------要么全部成功,要么全部回滚,绝不允许系统停在某个中间状态。

完整流程分为三步

  1. 依赖解析:计算要安装/升级/删除的包集合;
  2. 事务测试(Transaction Test)在不动真实文件系统的前提下完成所有校验这里就是 Payload SHA256 digest: BADTransaction test error 发生的关键阶段
  3. 事务执行:只有事务测试完全通过,才进入真正安装流程。

特别说明:关于文章开头提到的错误,涉及 YUM/DNF 的校验行为------正如 RPM 社区一项 PR(#3736)所讨论的,YUM/DNF 在正式安装前会执行一次"测试事务"(test-transaction),导致每个包的摘要被校验了两次。为了解决潜在的性能开销,PR 中提出在测试事务阶段跳过某些摘要计算。

3.2 校验流程分步详解

阶段 操作 失败时的典型错误
第 1 步:仓库元数据验证 下载 repodata/repomd.xml 及其校验和,对比本地计算值 metadata file does not match checksum
第 2 步:Header 摘要验证 解析 RPM Signature 段,对比 SHA256HEADER / SHA1HEADER Header SHA256 digest: BAD
第 3 步:Payload 压缩数据验证 计算压缩后 Payload 的 SHA256,对比 PAYLOADDIGEST Payload SHA256 digest: BAD
第 4 步:Payload 解压后验证(若启用) Payload 解压后重新计算 SHA256,对比 PAYLOADDIGESTALT Payload SHA256 ALT digest: BAD
第 5 步:GPG 签名验证 验证 OpenPGP 签名是否有效且来自可信公钥 NOKEY / BAD

RPM 的安全架构文档指出,在包验证时,RPM 检查:Header 摘以确保 Header 完整性,Payload 摘以确保包内容完整性。

验证输出示例:

复制代码
/data/RPMS/hello-2.0-1.x86_64-signed.rpm:
  Header OpenPGP V4 RSA/SHA256 signature, key fingerprint: 771b18d3...: OK
  Header SHA256 digest: OK
  Payload SHA256 digest: OK
  Legacy OpenPGP V4 RSA/SHA256 signature, key fingerprint: 771b18d3...: OK

3.3 事务测试阶段的校验缓存与性能问题

RPM 社区的一项发现指出:客户端(如 YUM/DNF)喜欢在真实的安装之前进行一次测试事务,导致验证被执行两次,从而重复计算包摘要

这意味着如果一个事务中包含大量软件包,摘要计算的开销会翻倍。虽然这不会影响最终结果(因为校验结果一致),但对于 CI/CD 流水线或具有更新大量包的环境来说,了解和规避这种性能开销非常重要。

四、校验失败的深度原因与场景化解决方案

4.1 诊断命令工具箱

命令 作用
rpmkeys -Kv package.rpm 首选诊断命令,查看包的摘要和签名状态
sha256sum package.rpm 计算整个 RPM 文件的 SHA256
`sha256sum $(rpm2cpio package.rpm cpio -i --to-stdout 2>/dev/null)`
rpm -qp --qf "%{PAYLOADDIGEST}\n" package.rpm(实验性) 查询 RPM 头中的 PAYLOADDIGEST 值(取决于 RPM 版本)
dnf clean all 清理所有缓存
dnf --refresh upgrade 强制刷新仓库元数据
mock --scrub=all 彻底清理 Mock 构建环境

4.2 场景化解决方案表

错误场景 根本原因 解决方案
Payload SHA256 digest: BAD单次出现 网络传输损坏或镜像节点缓存不一致 dnf clean packages && dnf --refresh 重新下载
Payload SHA256 digest: BAD多个包持续出现 仓库镜像节点长时间不同步 切换 baseurl 到另一个稳定镜像;联系仓库维护者同步 repodata
Payload SHA256 ALT digest: BAD 压缩 Payload 校验通过但解压后数据不匹配 罕见,通常表示 Payload 内部损坏较深;重新下载且换镜像节点
Header SHA256 digest: BAD 包元数据本身损坏 文件级损坏,重新下载
NOKEY GPG 公钥未导入 rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-*
mock 中的 Transaction test error Mock chroot 缓存损坏或仓库元数据不一致 mock --scrub=all 彻底清理,让 mock 重新构建环境

4.3 镜像延迟导致的校验失败

错误信息 [MIRROR] ... Downloading successful, but checksum doesn't match. Expected: 667463e8... != actual 发生时,常见原因是:

  • 部分镜像节点处于"元数据最新,但 RPM 文件还停留在旧版"的不一致状态
  • 客户端根据 repomd.xml 访问了某一个镜像节点,拿到旧版文件,而 repomd.xml 中的哈希对新版文件才正确
  • 解决方案:修改 .repo 文件中 mirrorlistbaseurl,排除问题镜像,或使用权威主仓库 URL

4.4 Mock 构建环境中的 Transaction test error

在 Mock 构建环境中,Transaction test error: package XXX does not verify: Payload SHA256 digest: BAD 发生在事务测试阶段。Mock 在开始构建 chroot 环境时,会进行事务测试来验证依赖完整性。此时出现校验失败的原因包括:

  • Mock 本地的包缓存目录(Cache)损坏
  • Mock 引用的仓库 repodata 与实际 RPM 不同步
  • Mock 的 chroot 环境处于不一致状态

最佳应对方案mock --scrub=all --target=aarch64 <your.config>,该操作彻底清理目标 chroot 环境和包缓存,让 Mock 重新从仓库获取所有内容。

五、参考资源

相关推荐
书生执笔画浮沉8 天前
rpmrebuild
linux·centos·rpm
蜡台9 天前
Centos 安装Mysql
linux·mysql·centos·yum·mysql8
lightqjx13 天前
【Linux】Linux工具(yum、vim、gcc/g++、make/makefile、gdb)的详细介绍
linux·vim·gdb·yum·gcc/g++·linux工具·make/makefile
HHFQ1 个月前
DNF 下载 RPM 依赖包及忽略特定依赖的方法
rpm·dnf
之歆2 个月前
RPM 包管理完全指南
rpm
virtualzzf2 个月前
OpenEuler 20.03构建zabbix8.0 rpm包
zabbix·openeuler·rpm·linxu
954L3 个月前
CentOs7执行yum update出现链接404问题
linux·centos·yum·vault
tianyuanwo3 个月前
Mock构建中RPM仓库校验和不匹配:深度解析与系统化解决方案
yum·rpm·checksum
muyan93 个月前
统信uos-server-20-1070e-arm64-20250704-1310 安装mysql-5.7.44
linux·mysql·yum·rpm·uos·统信