Git 3.0 即将默认使用 SHA-256 将是一次代价高昂的错误

Lobsters Hottest 新闻

摘要

GitButler 认为,Git 3.0 即将把默认哈希算法从 SHA-1 切换到 SHA-256,将对开发者造成代价高昂且全球性的破坏性变更,而实际收益甚微,因为在 Git 仓库中 SHA-1 碰撞实际上仍然无法被利用。

<p><a href="https://lobste.rs/s/bytzgl/git_3_0_s_upcoming_sha_256_default_will_be">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/10/01 20:29

# Git 3.0 即将到来的 SHA-256 默认值将是一个代价高昂的错误 来源:https://blog.gitbutler.com/git-3-sha-256 从何说起?这个话题我已经搁置了好几年,主要是因为有更聪明的人一直在专注研究这个问题,而我不太喜欢当"马后炮"。但我认为,Git 3.0 版本即将让大家付出大量时间和焦虑,却几乎得不到什么好处,而且几乎没有人知道即将发生什么。所以,抓起爆米花,听我讲一个故事:Git 3.0 中即将推出的几个破坏性变更之一(https://git-scm.com/docs/BreakingChanges#_changes),如何将变成一场巨大的、代价高昂的、全球性的灾难,却几乎没有任何实际价值。 我会尽量简短,因为你们中很多人可能已经了解基本原理。 Git 是一个所谓的内容可寻址数据库。这意味着如果你想在里面存储和传输数据,Git 会计算内容的哈希值,并在键/值数据库中将其用作键(值即为内容本身)。相同的内容在全球范围内永远会得到相同的哈希值。 **Git 在其键值对象数据库中使用 SHA-1 哈希作为键** **Git 在其键值对象数据库中使用 SHA-1 哈希作为键** 这很好,因为这意味着相同的文件内容永远不会被存储两次。还有一个很酷的特性:提交会编码前一个提交的哈希值,这意味着这种完整性实际上会**传播**——你无法改变任何东西的哈希值而不改变其后所有内容的哈希值。这赋予了它"密码学完整性",也就是说,对最新提交进行哈希,实际上也就对它之前可能数百万个文件内容、树和提交进行了哈希。 在 Git 中,这个哈希函数一直是 SHA-1。这是 Linus 在 2005 年 Git 创立时选择的,二十年来它表现得相当好——它相对快速,而且在实际意义上不可能有两个不同的文件意外地哈希到相同的值。事实上,据我所知,这在 Git 历史上从未发生过——在每一个仓库中创建的每一个文件、树和提交中,数十亿、数十亿个记录中,从来没有过。 从数学上讲,对于 SHA-1 的 160 位输出,生日界意味着你需要大约 **1.4 千万亿亿**个随机文件(1.4 quadrillion billion 个文件——1,400,000,000,000,000 个十亿——根本无法用语言有效描述)放在*单个项目*中,才能让文件哈希值意外地发生碰撞。 但问题在于,数学上,SHA-1 现在被认为是半"已破解"的,因为已经发表了碰撞攻击——**SHAttered**(https://eprint.iacr.org/2017/190,2017 年)和**SHA-1 is a Shambles**(https://sha-mbles.github.io/,2020 年)。虽然在任何已展示的方式中都还不具备实际利用价值,但理论上已经变得可能了。 因此,作为对这一问题的回应,在众多聪明人投入大量工作之后,即将发布的 Git 3.0 版本计划将其默认哈希算法从半"已破解"的 SHA-1 更换为更强大的 SHA-256 算法。 但首先,让我们暂停一下。在深入探讨之前,"已破解"到底意味着什么? 理解这一点很重要,因为它可能不是普通人所认为的"已破解"的含义。 从密码学哈希的角度来看,这里的"已破解"本质上意味着找到碰撞**并非不可能**。换句话说,如果你在这个问题上投入足够的资金和 GPU,那么对于某些内容形状,你可以故意制造出两个哈希值完全相同的不同东西。 **一个"已破解"的 SHA-lor 能做什么?凌晨时分……** **一个"已破解"的 SHA-lor 能做什么?凌晨时分……** 这意味着,虽然*意外地*让两个内容不同的合理文件发生碰撞仍然几乎不可能,但*刻意制造*两个哈希值相同的文件在技术上并非不可能。这意味着理论上存在攻击向量:有人可以用恶意版本替换某个文件的内容,而 Git 无法分辨,因为哈希值在数学上是相同的。 这些论文表明,SHA-1 具有一种性质,在理论上可以被现代 GPU 集群利用,以今天大约几万美元的成本制造故意碰撞,而 SHA-256 没有这种性质(如果你超级超级在意的话,可以在 Google 上搜索"linear message schedule sha-1 vs sha-256"——https://www.google.com/search?q=linear+message+schedule+sha-1+vs+sha-256……)。 听起来真的很可怕和令人担忧,对吧?*谁来想想那些孩子啊!?* 嗯,其实没那么严重。但让我们花一分钟时间,退一步来谈谈这些碰撞。 在考虑对哈希函数的攻击时,主要存在两个问题(假设我必须极度、愚蠢地简化来说)。一个是"碰撞攻击",另一个是"第二原像攻击"。 **碰撞攻击**就像你明知自己会当攻击者,但在获得信任之前假装自己是个好人。你故意生成两个哈希值相同的文件——一个是良性的,另一个是恶意的。你把良性文件给人们,直到你获得信任,然后把它换成恶意文件,因为哈希值匹配,而且你知道 Git 无法分辨两者的区别。你甚至可以在包含良性文件的树上获得已签名的标签或提交,让恶意文件看起来像是被签名过。 **第二原像攻击**是这样的:你看到一个你想替换的文件,然后创建第二个做恶意行为的文件,而这个文件*恰好*匹配那个哈希值,这样你就可以让人们在不知情的情况下拉取它。 重要的是,这*并不*意味着原始文件的作者必须也是攻击者。 **碰撞与第二原像的对比** **碰撞与第二原像的对比** 所以,在这场长篇大论开始之前,我最想强调的一点是:虽然第二原像攻击更加令人担忧,但几乎所有广泛使用的哈希函数都对其具有免疫力。Git 即使使用 MD5(被认为是一种极度破碎的哈希函数),仍然能够有效免疫第二原像攻击。再次强调,MD5 被认为是一种*完全"已破解"*的哈希函数,而 SHA-1 不是——它强得多。 因此,任何现实中有意义的攻击向量都依赖于碰撞攻击,这意味着引入原始文件的人也预先计算好了恶意版本,并打算在其被接受后注入。 然而,让我们极其、不切实际地保守一些,甚至为了辩论而假设*原像*是*容易*得到的。假设你找到了一种方法,可以在你的笔记本电脑上用一小时创建一个匹配任何已知文件哈希值的第二原像。现在你可以瞄准任何你想替换的文件,毫不费力地造出一个匹配哈希值的不同恶意文件。 **恭喜!你现在可以攻陷地球上任何代码库了!** 呃。嗯,有一个小问题。你如何: 1. 让那些不认识你、且从未拉取过此内容之前版本的人去获取这个文件 2. 让他们以一种对你有用的方式运行它 之后的每一个论点和问题都取决于对这两个问题的回答,然而这些问题的答案通常不在此问题的讨论范围之内。 即使我们假设 SHA-1 轻而易举就能破解,即使我们假设第二原像的生成是可能的(甚至是廉价的),这也不会使可用的攻击向量变得非常容易利用。 原因是:哈希*并不是* SCM 世界中信任的真正机制。它当然提供了密码学完整性的一个要素,但它并不是*信任*的根本基础。 Linus 在 Git 诞生时就明确论证了这一点(https://lore.kernel.org/git/[email protected]/)。 > 我真的认为人们不应该把 sha1 当作"安全"。真正的安全在于分发。 Linus Torvalds,2005 年(https://lore.kernel.org/git/[email protected]/) 信任基于"**你从哪里拉取?**",而且一直如此。 花一分钟时间,让我们考虑一下在将不受信任的内容引入代码库的世界中,*真正的攻击*是什么样子的。 当然,这是我们所讨论的最糟糕的场景。 某个源代码文件(或者在哈希攻击的情况下,更可能是二进制文件)被恶意插入,而你毫不知情。 事实证明,这种情况在现实中相当频繁地发生。不是因为人们花费数十万美元购买 GPU 来生成随机熵,在空字节后插入,以便某个二进制文件能碰巧匹配一个 SHA-1 校验和。 不,它目前在现实世界中发生的原因是:有人通过社会工程手段获取了写权限,入侵了一个被数百万个项目使用的 npm 包。现在困难的不是一个难以检测的二进制文件,而是依赖中每一个文件——每一个在其 `package.json` 文件中包含该项目的项目,都会盲目拉取这个依赖,而没有任何之前的校验和匹配。 这可能比试图暴力破解哈希碰撞并操纵不受信任的拉取**简单十亿倍**,成本更低,成功率更高。 如果我想把不受信任的代码引入 Android,*简单得多*的方法是贿赂或说服一个流行下游项目的维护者,接管它,然后将难以检测的代码注入到已经受信任的源 URL 中,而不是试图设计一个*容易被检测*的哈希碰撞,并将其放入一个*没有人会从中拉取*的 URL 中。 你认为不存在某个疲惫的开源维护者,不愿意以 4 万美元一次性付款的代价交出一个被广泛使用但不受喜爱的项目的维护权吗? **瞧,现在你不需要租 GPU 了,只需一天时间,你就可以用任何你想要的内容替换任何文件。** 换句话说,哈希碰撞攻击可能是最愚蠢的方式,当你还存在无偿的开源维护者和低信任度的包管理平台时,它可能是将不受信任的代码弄上系统的最愚蠢方式。 回到 Linus 的论点:我从 https://github.com/rust-lang/rust 拉取代码,不是因为我信任那个签名了最新提交 SHA 的 GPG 签名,然后假设任何随机来源都没问题。我从那里拉取代码,是因为我相信 GitHub 的认证机制足够完善,恶意的人在维护者不知情的情况下推送内容到那里是不太可能的。 这也是为什么我不会从 https://randomhash.onion/hAAAxx0r/rust 拉取代码,仅仅因为某封邮件让我这么做。 我其实并不在意哈希算法是什么,也不在意生成一个 Rust 显然不会提交到仓库中的大二进制文件的哈希碰撞可能少花了多少十亿年的计算时间。 说真的,这只是一个荒谬的前提。 我所知道的每一个为整个行业即将被迫进行的、代价昂贵的 SHA-256 迁移工作辩护的攻击场景,对我来说都是不切实际的。它们都可以通过许多其他更强、更简单的来源和内容保护手段来轻松、廉价地缓解,而不是用略微更强的哈希算法。 如果我们认为 SHA-1 只是一个足够好、足够快的方法,可以在受信任的仓库中为内容生成唯一键,并接受这些哈希*并不*天生用于信任本身(https://lore.kernel.org/git/[email protected]/),那么就没有真正理由替换它。 我们甚至可以使用 MD5,说实话可能也完全够用。 意外的内容碰撞在任何合理的代码库中在数学上都是几乎不可能的,其余的可以通过已签名的内容验证、外部认证和社会信任机制来处理。 如果我们不接受这一点,那么我们将陷入一个循环:每当有新的论文发表,有人发现了理论上的碰撞漏洞,我们就需要再次迁移整个 Git 生态系统。 我们可以经历多年的 SHA-1 到 SHA-256 迁移,然后量子计算机破解了 256,我们又回到了同样的愚蠢困境中。 仅仅因为我们把密码学完整性与信任混为一谈了。 现在等一下 Scott,你说这将是一场可怕的灾难。这一定是夸张了吧。 可能是。但无论你如何计算成本,它都不会便宜或简单。 Emily Shaffer 刚刚做了一个演讲(https://www.youtube.com/watch?v=eJJp0RE7cd4),讲述 Google 如何准备应对这个问题,情况并不乐观。 让我们分解一下当 Git 3.0 带着这个新默认值发布时会发生什么。 第一件事是,所有通过 `git init` 创建的新仓库都将使用 SHA-256 内容哈希算法创建。 你今天就可以自己尝试,运行 `git init --object-format=sha256`: ``` ❯ git init --object-format=sha256 /tmp/twofiddy Initialized empty Git repository in /private/tmp/twofiddy/.git/ ❯ cd /tmp/twofiddy ❯ echo 'sha 256' > README.md ❯ git add README.md; git commit -am 'first' [main (root-commit) 11043f6] first 1 file changed, 1 insertion(+) create mode 100644 README.md ❯ git log commit 11043f6a3be7d21e999dc84550886306bee65f4faf4fc9226979108fd1a0b1af (HEAD -> main) Author: Scott Chacon Date: Wed Sep 30 10:36:13 2026 +0200 first ``` 你会注意到的第一件事(除了哈希值长得多之外),就是你无法将这段代码推送到 GitHub,尽管在 Git 3.0 发布时这个问题几乎肯定会得到修复。 事实上,这可能就是目前完全延迟 3.0 发布的主要原因。 但当你确实想把它推送到 GitHub(或任何托管平台)时,你需要在服务器上创建仓库时告诉他们这是一个 sha256 项目。 每个项目现在要么属于其中一个桶,要么属于另一个,它们不能混合。 这会立即让很多人感到沮丧,因为他们现在需要*知道*自己用的是哪个版本的 git 运行了 `git init`,并且在去 GitHub 创建服务器仓库时,确保选择了正确的选项。 在托管平台上创建新仓库将需要看起来像这样,但你怎么知道你需要哪一个? 另外,哪个正常人会知道这意味着什么,或者知道选择哪一个有*疯狂的*重要性? **在托管平台上创建新仓库将需要看起来像这样,但你怎么知道你需要哪一个?另外,哪个正常人会知道这意味着什么,或者知道选择哪一个有 _疯狂的_ 重要性?** 我们即将看到*大量*这样的情况: ``` ❯ git push origin main fatal: the receiving end does not support this repository's hash algorithm ``` 出于几个不同的原因。因为根据托管平台如何标记这个选项,很难知道你需要上游仓库以哪种格式初始化。 所以你可能会在上游创建 256 格式,然后尝试推送一个用 3.0 之前的二进制文件初始化的本地仓库。或者明显反过来。 ### 库、链接和工具,哦天哪! 此外,对于库来说,这*确实*非常成问题,因为子模块只能与相同类型的项目一起使用,所以它们将需要有两个版本,以便既被现有项目使用,也被新项目使用。 托管平台可能通过保留另一种格式的镜像来同时处理两种格式,但这将增加 GitHub 等网站上几乎每个操作的负载*,并且*还会进一步使信任问题复杂化。 对于决定将 SHA-1 转换为 SHA-256 的*现有*项目,他们需要将项目中的每一个对象转换为新格式,这将破坏所有现有的签名。 而且在该平台上工作或使用该项目的所有人都需要同时切换过来,以避免分裂头的问题。 或者,再次,通过镜像设置,将写入权限从一个切换到另一个。 这仍然没有解决那些没有 256 版本的其他镜像的对象替换问题。 如果他们*确实*进行了转换,那么所有现有的 URL 或 Slack、邮件或其他任何地方的链接都将……

相似文章

展望 Git 2.56 与 3.0

Hacker News Top

Git 2.56 是一个可靠的版本,带有小幅改进,而 Git 3.0 预计将带来重大变化,包括切换到 SHA-256 哈希。

将 Git 仓库迁移到 SHA-256

Lobsters Hottest

一篇关于将 Git 仓库迁移到 SHA-256 对象哈希的文章,不过正文似乎是损坏的占位文本,而非实质性内容。

Git哈希链可塑性

Hacker News Top

一篇研究论文揭示,Git提交哈希可以被篡改,从而生成第二个具有不同哈希但内容相同且签名有效的有效提交,这破坏了提交哈希完整性和供应链安全性。