ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

解决pip哈希校验失败:从报错到Python依赖管理加固

2026/10/4 2:12:48 拓冰建站 浏览量
解决pip哈希校验失败:从报错到Python依赖管理加固 先把这个报错当成一次机会来看它其实是 pip 在替你守门。做 Python 项目交付的人八成都在某个深夜或者某条 CI 流水线上见过这行刺眼的红字ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE.翻译成人话就是pip 在安装依赖时发现 requirements.txt 里锁定的哈希值和它实际下载到的文件算出来的哈希对不上。听起来像个小问题但它会让整个 pip install 直接失败一个依赖都不装——CI 挂、容器构建挂、本地环境同步挂。我第一次遇到时第一反应是换个源重装结果换完还报后来才意识到根因完全不在源上。这不是 pip 在闹脾气恰恰相反这是 pip 在正常工作。哈希校验是 pip 用来防止依赖被篡改的安全机制报错恰恰说明它发现了一个不该出现的变化。把这套机制搞懂你不仅能快速修复这条报错还能顺手把项目的依赖管理做得更扎实。这篇文章适合所有用 Python 做开发、维护 CI/CD 流水线、或者负责应用打包交付的人下面我把触发场景、根本原因、五种解决方案和一个完整的实战排查案例一次讲清楚。1. 先搞清楚报错场景哈希校验模式是怎么被触发的1.1 三种最常见的触发方式这条报错只在 pip 的哈希校验模式hash-checking mode下才会出现。你可以在三种情况下进入这个模式requirements.txt 文件第一行或开头写了--require-hashesrequirements.txt 里任意一个依赖行后面带了--hashsha256:xxxx这样的参数命令行执行 pip install 时手动加了--require-hashes。注意一个容易忽略的规则只要 requirements.txt 里出现了哪怕一个--hashpip 就会自动进入哈希校验模式不管你命令行有没有加--require-hashes。这是 pip 文档里的明确约定很多人没意识到这一点导致我明明没要求校验哈希啊的困惑。一个典型的带哈希的 requirements.txt 长这样--require-hashes requests2.31.0 \ --hashsha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hashsha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1这种文件通常不是手写的而是用 pip-tools、hashin 这类工具自动生成的目的就是锁定依赖的精确产物保证每一次安装的内容完全一致。1.2 完整报错信息逐行解析完整的报错比标题那行长得多信息量也大得多。我贴一段真实场景下的报错ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE. If you have updated the package versions, please update the hashes. Otherwise, examine the package contents carefully; someone may have tampered with them. requests from https://pypi.org/simple/requests/: Expected sha256 58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f Got sha256 942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1逐行拆解一下重点第一段话是 pip 的标准提示前半句如果你更新了包版本请同步更新哈希是针对最常见原因的后半句请仔细检查包内容可能有人动手脚是安全提示第二行requests from https://pypi.org/simple/requests/告诉你是哪个包、从哪个源下载的Expected sha256是 requirements.txt 里锁定的哈希Got sha256是 pip 实际下载到的文件计算出的哈希。Expected 和 Got 不一致这就是全部矛盾所在。接下来要做的不是对着屏幕发愁而是先判断一个问题这两串哈希谁是对的2. 为什么哈希会对不上从根上排查2.1 版本漂移锁的是旧内容源上已经是新文件这是最普遍的原因。有人把依赖版本号写成requests2.31.0哈希也锁定了当时 PyPI 上 2.31.0 对应的文件。但某些镜像源或企业内部的私有源可能缓存的构建产物和官方源不完全一致或者某个包在同一个版本号下重新发布了新构建文件这种情况在部分维护不规范的包身上真的发生过。于是 pip 下载到的文件变了哈希自然对不上。遇到这种情况一般重新生成一次哈希就能解决。但要注意如果同一个版本号在源上确实换了产物那意味着同一版本号可能装到不同内容这件事本身就值得警惕说明这个源或这个包的发布流程不太靠谱。2.2 镜像源不同不同源提供的构建产物不一样很多公司内部有 PyPI 镜像或者开发者习惯用国内镜像加速。同一个包在官方 PyPI 上是 manylinux 的 wheel在某个镜像上可能被同步成了源码包 sdist或者因为同步延迟镜像上还是旧版本的 wheel。文件不同哈希就不同。这就是为什么我在后面会特别强调生成哈希的源必须和实际安装用的源保持一致。否则你拿着一份在阿里云镜像上生成的哈希去官方 PyPI 上安装哈希对不上是必然事件。2.3 平台差异不同系统、不同 Python 版本的 wheel 天然不同这一点是新手最容易踩的坑。py3-none-any这种纯 Python 通用 wheel 哈希全网一致但很多带 C 扩展的包会根据平台提供不同的 wheel例如Windows 上有win_amd64的 wheelmacOS 有macosx_10_9_x86_64的 wheelLinux 有manylinux2014_x86_64的 wheel。这些文件的哈希值是完全不同的。如果你的 requirements.txt 里只锁了 macOS 上生成的 wheel 哈希换到 Linux CI 机器上安装pip 会下载 Linux 的 wheel哈希立刻对不上。同一份 requirements 不能在多个平台间这么硬搬。2.4 本地缓存损坏断网、磁盘 IO 异常导致的坏缓存pip 默认会把下载的包缓存到本地下次安装直接用缓存。如果缓存文件在写入时出了问题比如磁盘满了、进程被杀、下载中断缓存里的包可能是个损坏的文件。这时候 pip 拿着损坏文件和 requirements 里的哈希比对结果自然是不匹配。这种情况有一个明显特征同一个报错在同一台机器上反复出现但是在干净环境或别人机器上又正常。如果你发现只有我的机器报错CI 上明明好的先怀疑缓存。2.5 安全风险源被篡改概率低但不能排除pip 报错提示someone may have tampered with them不是吓唬人。如果以上所有原因都排除了——版本没动、源没换、缓存清了、平台一致——哈希仍然对不上那确实存在依赖被篡改的可能性。这在大型公共镜像被入侵的事件里真实发生过。遇到这种情况建议立刻做两件事一是换官方源重新下载比对哈希二是核对一下这个包的发布者信息和发布时间是否正常。大多数时候是虚惊一场但这个检查动作不能省。下面把常见原因和排查方向整理成一张速查表方便你对照可能原因典型特征排查方向版本漂移 / 源上产物更新同一版本号下哈希变了重新生成哈希镜像源不同换了源之后开始报错固定源并重新生成哈希平台 / 系统差异requirements 在别的机器上生成的按目标平台重新生成哈希本地缓存损坏只有本机报错干净环境正常清缓存重试依赖被篡改所有常规原因排除后仍报错换官方源核对谨慎处理3. 解决方案实操从最推荐到应急3.1 方案一重新生成哈希并更新 requirements.txt最推荐这是最正规、也是唯一值得长期使用的解法。核心思路是让 pip 从你指定的源下载正确的包文件计算出新哈希再写回 requirements.txt。第一步下载包文件但不安装pip download requests2.31.0 -d /tmp/pkg --no-deps第二步用 pip 自带命令计算哈希pip hash /tmp/pkg/requests-2.31.0-py3-none-any.whl输出会是这样/tmp/pkg/requests-2.31.0-py3-none-any.whl: --hashsha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1把输出的--hash...复制到 requirements.txt 对应依赖行后替换掉旧哈希即可。如果依赖很多逐个手动替换不现实推荐用 hashin 这个工具自动完成pip install hashin hashin requests2.31.0 -r requirements.txthashin 会从 PyPI 拉取当前版本的所有可用文件把全部平台的哈希都写进去同时自动把旧哈希替换掉。生成的 requirements 在不同平台上安装时都能匹配到对应的哈希省心很多。如果你原本就是用 pip-tools 管理依赖更简单的做法是直接用 pip-compile 重新生成整个文件pip install pip-tools pip-compile --generate-hashes requirements.in -o requirements.txtrequirements.in 里只写顶层依赖pip-compile 会自动解析出完整依赖树并生成带哈希的 requirements.txt。这个方案最大的好处是哈希永远和当前锁定的版本一一对应不会出现版本升了哈希忘更的问题。3.2 方案二临时绕过哈希校验只用于排查定位有时候你只是想快速确认是不是哈希的问题而不是真的想绕过安全机制。这时候可以临时去掉校验让安装先跑通把问题定位出来。最稳妥的临时做法是复制一份 requirements 文件把里面所有的--hashxxx和开头的--require-hashes删掉再用这个临时文件安装cp requirements.txt requirements_nohash.txt sed -i s/ \\//; s/--hashsha256:[a-f0-9]*//g requirements_nohash.txt pip install -r requirements_nohash.txt注意pip 在安装时要求所有依赖行格式正确你手动删哈希时要小心不要把换行结构弄坏。如果文件是带反斜杠续行的格式像我上面 sed 命令里处理的那样要先处理续行符再处理哈希字段。这里必须强调这个方案只适合本地快速验证千万不要在 CI 流水线或者生产环境这么干。哈希校验是依赖供应链安全的重要防线删掉它等于把门锁拆了。我见过有人图省事直接在 Dockerfile 里加跳过校验的参数后来团队查一个诡异的环境问题时才发现是依赖被污染了那叫一个后悔。3.3 方案三清理 pip 缓存后重试如果你判断报错和缓存损坏有关先看缓存再清理。查看缓存现状pip cache info pip cache list | grep requests只清理某个包pip cache remove requests或者干脆全部清掉pip cache purge如果你的 pip 版本比较老20.1 之前没有 cache 子命令直接加--no-cache-dir绕开缓存安装一次也能达到同样效果pip install -r requirements.txt --require-hashes --no-cache-dir每次安装都--no-cache-dir不推荐但作为一次性排查手段非常有效。如果清了缓存后问题消失基本可以确认是缓存文件损坏。3.4 方案四固定镜像源并保持哈希一致既然哈希和源强相关那就把源彻底固定下来。常见做法是在 pip 配置里写死 index-url比如在pip.confLinux/macOS或pip.iniWindows中配置[global] index-url https://mirrors.aliyun.com/pypi/simple/ trusted-host mirrors.aliyun.com配置好后有两个动作要做一是所有生成哈希的操作都要基于这个源二是同一份 requirements 在所有使用方那里都用同一个源。只要源统一了哈希不一致的概率会大幅降低。顺便提醒一个实际操作细节如果你用官方 PyPI 生成哈希但又用内网镜像安装即使镜像内容同步得很完整也可能因为同步时间差导致临时性不一致。这时候不要急着改哈希先确认镜像是否已经同步到你锁定的版本文件。3.5 方案五改用锁文件方案从源头避免反复踩坑哈希报错反复出现本质上是手工维护哈希这件事太容易出错。更彻底的解决办法是把哈希生成交给工具自动管理。两个主流选择pip-tools用 requirements.in 声明依赖pip-compile 生成带哈希的 requirements.txt升级依赖时重新执行一次编译即可poetry用 pyproject.toml 声明依赖poetry.lock 自动记录每个依赖的精确版本和哈希团队直接基于 poetry.lock 安装。这两个工具我都深度用过个人体会是pip-tools 更贴近传统 pip 工作流迁移成本低poetry 功能更全面但如果你只是想要哈希锁定pip-tools 就够用了。关键收获是引入锁文件工具后我再也没遇到过哈希忘更新这类人为失误。4. 实战复盘一次 CI 构建失败的完整排查过程理论讲再多不如走一遍真实排查流程。下面是我最近处理过的一个典型案例按时间线完整记录。4.1 问题复现与日志收集项目是一个内部 API 服务CI 流水线第一步就是创建虚拟环境并安装依赖。某天构建开始报错日志尾部是这样的Collecting requests2.31.0 Downloading requests-2.31.0-py3-none-any.whl (62 kB) ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE. requests from https://mirrors.internal.example.com/pypi/web/simple/requests/: Expected sha256 58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f Got sha256 942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1第一件事不是改代码而是把关键信息记录下来报错包名、expected 哈希、got 哈希、下载源地址。这些信息后面每一步排查都会用到。4.2 定性用排除法缩小原因范围我按这个顺序过了一遍确认 requirements.txt 最近有没有人动过。查 git 记录发现这份 requirements 是三个月前用 hashin 生成的期间没人改过确认版本有没有漂移。requests2.31.0是写死的PyPI 上这个版本也没有重新发布过新构建确认平台有没有变。CI 一直是同一个 Ubuntu runner平台没变检查缓存。CI 是每次全新环境不涉及本地缓存怀疑点落在了镜像源上。进一步看日志里的下载地址mirrors.internal.example.com这是公司内部镜像。我顺手对比了一下用同样的版本在官方 PyPI 上手动下载哈希和 requirements 里锁定的完全一致。也就是说requirements 没问题问题出在内部镜像提供的文件。最后查到的原因是运维那边升级了内部镜像的存储后端重新同步了一批元数据可能拉取到了一份缓存中残留的旧构建产物。镜像上的文件和官方源不一致哈希自然对不上。4.3 生成正确哈希并修复原因定性之后修复方案很明确以内部镜像为准重新生成哈希并更新 requirements同时向运维反馈镜像异常。我先把包从内部镜像下载下来pip download requests2.31.0 -d /tmp/pkg --no-deps -i https://mirrors.internal.example.com/pypi/simple/然后用 sha256sum 做了一次独立验证sha256sum /tmp/pkg/requests-2.31.0-py3-none-any.whl结果和报错信息里的 Got 值一致确认这个文件就是当前镜像上真实存在的产物。接着用 pip hash 生成新哈希pip hash /tmp/pkg/requests-2.31.0-py3-none-any.whl把新哈希替换进 requirements.txt 后再次触发 CI 构建安装阶段顺利通过。4.4 验证与后续加固修复完成只是第一步。为了不让同类问题再次卡住构建我做了三件加固的事在 CI 里加了构建缓存清理步骤确保每次安装都从镜像拉取最新文件联系运维核对镜像同步机制确认后续不会再出现元数据和实际文件不一致的情况在团队文档里明确写了一条规范任何依赖的哈希变更必须记录变更原因不能只改文件不留说明。这次排查给我最大的教训是哈希对不上先不要急着重新生成哈希一把梭因为这个动作会把真正的异常掩盖掉。先判断哈希不一致是正常变更还是异常变更再去改文件顺序不能反。5. 常见问题速查与避坑清单5.1 常见问题速查表现象可能原因首选处理方式换了个机器就报错平台不同wheel 哈希不同用 hashin 补全所有平台哈希同一台机器突然报错镜像源内容更新 / 缓存损坏先清缓存再检查镜像源升级依赖后报错版本更新但哈希没更新重新生成哈希公司镜像源报错镜像同步异常或内容不一致核对官方源哈希反馈给运维只改了一个包却全盘报错可能依赖树里有间接依赖哈希过期用 pip-compile 重新生成整个文件5.2 新手最容易踩的几个坑第一个坑修改 requirements.txt 时把续行符和哈希格式弄坏。带哈希的文件每行依赖后面通常有反斜杠续行手改时少一个空格或者换行符位置不对pip 会报解析 requirements 文件失败之类的错误。这种问题比哈希不匹配还难排查因为报错信息指不到具体行。所以能用工具生成就别手改。第二个坑只锁一个平台的哈希然后到处复制这份文件。前面说过不同平台 wheel 哈希不同正确的做法是用 hashin 生成包含所有平台哈希的文件或者在每个目标平台上分别生成。第三个坑遇到报错就加--no-deps或者直接删掉依赖。这种解决不了问题就解决提出问题的人的思路短期能绕过去长期会把依赖关系搞乱。哈希校验绕过去之后你等于放弃了对供应链安全的检查这在企业级项目里是不能接受的。第四个坑把--require-hashes模式和普通锁定版本混为一谈。锁定版本号只能保证版本一致不能保证文件内容一致哈希才能精确到文件级别。如果你的项目对安全要求高必须用哈希不能只锁版本号。5.3 我在实际使用中的一些习惯踩过几次坑之后我现在形成了几个固定习惯分享给你参考。生成哈希统一用一个源。我通常直接固定官方 PyPI 或者公司唯一指定的内部镜像生成哈希和实际安装必须走同一个源这一点写进了团队的流水线模板。升级依赖后必重新编译锁文件。不管是用 hashin 还是 pip-compile升级任何依赖之后都要重新生成哈希并且把新旧哈希变更提交到 MR 里一起 review。这样每次哈希变化都是可见的、可追溯的。CI 里加一道哈希校验的确认步骤。具体做法是安装时不加--no-cache-dir但定期全量清理一次缓存。这样既能保证安装速度又能避免缓存垃圾导致偶发的哈希问题。如果有一天你排查到深夜实在没思路记住一个最简单的自检顺序先确认 requirements 文件本身有没有被人改过再确认包在源上是否还是原来那个文件最后清一次缓存重试。八成的问题都能在这三步之内找到答案。剩下两成基本就是源的问题找运维核对就好不用自己硬扛。