【问题标题】:How would Git handle a SHA-1 collision on a blob?Git 如何处理 blob 上的 SHA-1 冲突?
【发布时间】:2012-03-12 15:17:49
【问题描述】:

这可能从未在现实世界中发生过,也可能永远不会发生,但让我们考虑一下:假设您有一个 git 存储库,进行提交,然后变得非常非常不幸:其中一个 blob 最终具有相同的SHA-1 作为另一个已经在您的存储库中。问题是,Git 将如何处理这个问题?只是失败?找到一种方法来链接这两个 blob,并根据上下文检查需要哪一个?

与其说是一个实际问题,不如说是一个脑筋急转弯,但我发现这个问题很有趣。

【问题讨论】:

  • 曾经是脑筋急转弯,现在可能是an actual problem
  • @Toby 这个问题是关于pre-image attack;谷歌展示的是collision attack——类似但略有不同。您可以阅读更多关于差异的信息here
  • @Toby 最初的脑筋急转弯不是关于一次攻击(既不是原像也不是碰撞),而是关于一次意外的碰撞,这种碰撞非常不可能,因此不值得考虑。我认为 Saheed 正确地试图说这仍然不是一个实际问题。但是,您是对的,Google 碰撞攻击可能会根据 Git 的使用方式造成安全问题。
  • 这是第二次冲突,只有 320 个字节 privacylog.blogspot.com/2019/12/the-second-sha-collision.html
  • 随着针对 SHA-1 的选择前缀攻击的发展;这正在进入可能的领域。 arstechnica.com/information-technology/2020/01/…

标签: git hash-collision


【解决方案1】:

根据Pro Git

如果您碰巧提交了一个与您的存储库中的先前对象具有相同 SHA-1 值的对象,Git 将在您的 Git 数据库中看到先前的对象并假定它已经被写入。如果您在某个时候再次尝试检查该对象,您将始终获得第一个对象的数据。

所以它不会失败,但它也不会保存您的新对象。
我不知道这在命令行上会是什么样子,但这肯定会令人困惑。

再往下一点,同一参考文献试图说明这种碰撞的可能性:

下面是一个示例,可让您了解发生 SHA-1 冲突需要采取的措施。如果地球上所有 65 亿人都在编程,并且每一秒,每个人都在生成相当于整个 Linux 内核历史(100 万个 Git 对象)的代码并将其推送到一个巨大的 Git 存储库中,那么这将需要 5 年时间直到该存储库包含足够的对象,以使单个 SHA-1 对象冲突的概率为 50%。您的编程团队的每个成员都更有可能在同一晚在不相关的事件中被狼袭击和杀死。

【讨论】:

  • 我想看看最后一句数字的来源 ;-)
  • @Jasper:该链接是很好的文档,但它确实包含有关团队中每个成员在不相关事件中被狼袭击和杀死的概率的统计数据同一个晚上。
  • @Jasper:嗯,按照我的阅读方式,文字确实声称 65 亿团队成员在同一晚被狼杀死的概率高于 50%。但我对他的说法的主要反对意见是,这样的事件必须 成为一种全球现象; 不可思议这可能是由于无关事件而发生的。 ;)
  • @KeithRobertson 我很确定这篇文章是在谈论如果世界上每个人都被吃掉的情况下,你所有的实际团队成员被吃掉的可能性与哈希冲突的可能性相比产生大量代码,以及在这种情况下达到 50% 的碰撞几率所需的时间(即狼事件并没有涉及整个世界,50% 与狼是分开的)。不过,您确实明白了这一点,如果这样的事件是不可想象的,那么 git 哈希冲突也应该如此。 (当然,一个是(几乎)纯粹基于机会的,另一个不是,但仍然是。)
【解决方案2】:

要添加到my previous answer from 2012,现在(2017 年 2 月,五年后)有一个实际 SHA-1 与shattered.io 冲突的示例,您可以在其中制作两个冲突的 PDF 文件:即获取一个 SHA第一个 PDF 文件上的 -1 数字签名,也可以作为第二个 PDF 文件上的有效签名被滥用。
另请参阅“At death’s door for years, widely used SHA1 function is now dead”和this illustration

2 月 26 日更新:Linus 确认了以下几点in a Google+ post

(1) 首先——天没有塌下来。将加密哈希用于安全签名之类的事情与使用加密哈希为像 git 这样的内容可寻址系统生成“内容标识符”之间存在很大差异。

(2) 其次,这种特殊 SHA1 攻击的性质意味着它实际上很容易缓解,并且已经发布了两组补丁用于缓解。

(3) 最后,实际上有一个相当简单的过渡到不会破坏世界的其他哈希 - 甚至是旧的 git 存储库。

关于该转换,请参阅 Q1 2018 Git 2.16 添加表示哈希算法的结构。该过渡的实施已经开始。

Starting Git 2.19 (Q3 2018),Git 已选择 SHA-256 作为 NewHash,并且正在将其集成到代码中(意味着 SHA1 仍然是默认值(2019 年第二季度,Git 2.21),但是SHA2 将是继任者)


原始答案(2 月 25 日) 但是:

Joey Hessa Git repohe found 中尝试那些pdf:

这包括两个具有相同 SHA 和大小的文件,它们确实得到 由于 git 将标题添加到 内容。

joey@darkstar:~/tmp/supercollider>sha1sum  bad.pdf good.pdf 
d00bbe65d80f6d53d5c15da7c6b4f0a655c5a86a  bad.pdf
d00bbe65d80f6d53d5c15da7c6b4f0a655c5a86a  good.pdf
joey@darkstar:~/tmp/supercollider>git ls-tree HEAD
100644 blob ca44e9913faf08d625346205e228e2265dd12b65    bad.pdf
100644 blob 5f90b67523865ad5b1391cb4a1c010d541c816c1    good.pdf

虽然将相同的数据附加到这些冲突文件确实会生成 其他冲突,前置数据不会。

所以main vector of attack (forging a commit) would be

  • 生成一个常规的提交对象;
  • 使用整个提交对象 + NUL 作为选择的前缀,并且
  • 使用相同前缀的碰撞攻击来生成碰撞的好/坏对象。
  • ...这没用,因为好的和坏的提交对象仍然指向同一棵树!

另外,您已经可以使用cr-marcstevens/sha1collisiondetection 检测每个文件中存在的针对 SHA-1 的密码分析冲突攻击

在 Git 本身中添加类似的检查 would have some computation cost

在更改哈希时,Linux comments

散列的大小和散列算法的选择是独立的问题。
你可能会做的是切换到 256 位哈希,使用它 在内部和本机 git 数据库中,然后默认情况下仅 显示哈希作为 40 个字符的十六进制字符串(有点像我们如何 在很多情况下已经缩写了)。
这样 git 周围的工具甚至看不到更改,除非传入 一些特殊的“--full-hash”参数(或“--abbrev=64”或其他 - 默认是我们缩写为 40)。

仍然是transition plan (from SHA1 to another hash function) would still be complex,但正在积极研究。
A convert-to-object_id campaignin progress


3 月 20 日更新:GitHub detail a possible attack and its protection:

可以通过各种机制为 SHA-1 名称分配信任。例如,Git 允许您对提交或标签进行加密签名。这样做只会对提交或标记对象本身进行签名,这反过来又通过使用它们的 SHA-1 名称指向包含实际文件数据的其他对象。这些对象中的冲突可能会产生一个看似有效的签名,但它指向的数据与签名者预期的数据不同。在这样的攻击中,签名者只能看到一半的碰撞,而受害者看到另一半。

保护:

最近的攻击使用特殊技术来利用 SHA-1 算法的弱点,从而在更短的时间内发现冲突。这些技术会在字节中留下一个模式,在计算碰撞对的任何一半的 SHA-1 时可以检测到该模式。

GitHub.com 现在对其计算的每个 SHA-1 执行此检测,如果有证据表明对象是碰撞对的一半,则中止操作。这可以防止攻击者使用 GitHub 来说服项目接受他们碰撞中“无辜”的一半,以及防止他们托管恶意的一半。

Marc Stevens的“sha1collisiondetection


同样,Q1 2018 Git 2.16 添加了表示哈希算法的结构,开始执行向新哈希的转换。
如上所述,新支持的 Hash 将是SHA-256

【讨论】:

  • 碰撞: 1. 试图制造碰撞,而不是巧合。 2. 来自 te PDF 报告:总共花费的计算工作量相当于 2^63.1 次 SHA-1 压缩,大约花费了 6,500 个 CPU 年和 100 个 GPU 年 3.虽然我们应该从 MD5 和 SHA-1 开始,但对于文件唯一用途来说,它们通常都很好。
  • 值得注意的是,WebKit 签入了冲突的 PDF 以进行测试。它破坏了他们的 git-svn 镜像基础设施:bugs.webkit.org/show_bug.cgi?id=168774#c24
  • @dahlbyk 确实值得注意......因为我在答案中注意到了它(“虽然git-svn确实有一些问题”背后的链接指的是它,尽管是间接的)
  • @Mr_and_Mrs_D 不,它还没有因错误而失败。一个大补丁正在进行中,这将有助于促进碰撞检测:marc.info/?l=git&m=148987267504882&w=2
  • @Mr_and_Mrs_D 在stackoverflow.com/posts/42450327/revisions 中看到编辑 4:它现在确实失败了,至少在上传到 GitHub 时是这样。
【解决方案3】:

原始答案 (2012)(请参阅下面的 shattered.io 2017 SHA1 冲突)

old (2006) answer from Linus 可能仍然相关:

不。如果它具有相同的SHA1,则意味着当我们从另一端接收到对象时,我们将不会覆盖我们已经拥有的对象。

所以发生的情况是,如果我们看到冲突,任何特定存储库中的“较早”对象总是会被覆盖。但请注意,“较早”显然是每个存储库的,因为 git 对象网络生成一个未完全排序的 DAG,因此虽然不同的存储库会就直接祖先的情况下的“较早”达成一致,如果对象来自单独且不直接相关的分支,两个不同的 repos 显然可能以不同的顺序获取了这两个对象。

但是,从安全角度来看,“早期将覆盖”是您非常想要的:请记住,git 模型是您应该主要只信任自己的自己的存储库。
因此,如果您执行“git pull”,则根据定义,新传入对象的可信度低于您已有的对象,因此允许新对象 换一个旧的。

所以你有两种碰撞情况:

  • 不经意的类型,你不知何故非常非常不走运,两个文件最终具有相同的 SHA1。
    那时,发生的情况是,当您提交该文件(或执行“git-update-index”将其移动到索引中,但尚未提交)时,将计算新内容的 SHA1,但 因为它匹配旧对象,不会创建新对象,并且 commit-or-index 最终指向 old 对象
    您不会立即注意到(因为索引将匹配旧对象 SHA1,这意味着“git diff”之类的内容将使用签出副本),但如果您曾经做过树级差异(或者您进行克隆、拉取或强制签出)您会突然注意到该文件已更改为与您预期的完全不同的东西完全
    因此,您通常会很快注意到这种碰撞。
    在相关新闻中,问题是如何处理意外碰撞..
    首先,让我提醒人们,这种无意的碰撞真的非常真的不太可能,所以我们很可能永远不会在整个宇宙历史中看到它。
    但是如果发生这种情况,这并不是世界末日:您最有可能要做的就是更改轻微碰撞的文件,然后强制使用更改后的新提交内容(添加一条评论说“/* This line added to avoid collision */”)然后向 git 介绍已被证明是危险的魔法 SHA1。
    所以在几百万年之后,也许我们必须在 git 中添加一两个“中毒”的 SHA1 值。这不太可能是维护问题;)

  • 攻击者类型的冲突,因为有人破坏(或暴力破解)SHA1。
    这显然比无意的类型更有可能很多,但根据定义,它始终是一个“远程”存储库。如果攻击者可以访问本地存储库,他将有更简单的方法来搞砸你。
    所以在这种情况下,冲突完全不是问题:你会得到一个与攻击者意图不同的“坏”存储库,但是因为你永远不会真正使用他的碰撞对象,字面意思与攻击者没有什么不同,只是根本没有发现碰撞,只是使用了你已经拥有的对象(即它 100% 等同于“微不足道”生成相同 SHA1 的相同文件的冲突)。

question of using SHA-256 经常被提及,但暂时不采取行动(2012 年)。
注意:starting 2018 and Git 2.19,正在重构代码以使用 SHA-256。


注意(幽默):您可以使用来自Brad Fitzpatrick (bradfitz) 的项目gitbrute 强制提交到特定的SHA1 前缀

gitbrute 暴力破解一对作者+提交者时间戳,以便生成的 git 提交具有您想要的前缀。

示例:https://github.com/bradfitz/deadbeef


Daniel Dinnyes 指出in the comments7.1 Git Tools - Revision Selection,其中包括:

您的编程团队的每个成员都更有可能在同一天晚上在不相关的事件中被狼袭击和杀死。


即使是最近(2017 年 2 月)shattered.io 也证明了伪造 SHA1 冲突的可能性:
(在我的 separate answer 中查看更多信息,包括 Linus Torvalds 的 Google+ 帖子)

  • a/ 仍然需要超过 9,223,372,036,854,775,808 次 SHA1 计算。这相当于 6,500 年的单 CPU 计算和 110 年的单 GPU 计算的处理能力。
  • b/ 将伪造 一个 文件(具有相同的 SHA1),但在附加约束的情况下,其内容 大小将产生相同的 SHA1(内容冲突单独是不够的):见“How is the git hash calculated?”):blob SHA1 is computed based on the content and size

请参阅 Valerie Anita Aurora 中的“Lifetimes of cryptographic hash functions”了解更多信息。
在该页面中,她指出:

Google 花费了 6500 个 CPU 年和 110 个 GPU 年来说服我们需要停止将 SHA-1 用于安全关键应用程序的所有人。
也因为它很酷

在我的 separate answer below 中查看更多信息。

【讨论】:

  • twist: 添加/* This line added to avoid collision */ 后仍然是相同的哈希值:D 你可以赢得两次彩票:P
  • @JanusTroelsen 当然,但它是still a lottery, is it not? ;)(如short note about SHA1 中所述)
  • @VonC 关于that reference:这是一场全球狼人流行病的爆发——消灭了所有人类,并导致我所有的开发人员在同一个晚上可怕地死亡,即使他们在地理上是分散的——视为无关事件??当然,假设它发生在满月,显然。现在,这种情况会改变事情。连想都想疯了!那是在一个完全不同的概率范围内!这意味着我们必须... 停止使用GIT!现在!!!每个人都 RUUUUUN!!!!!!!
  • 请注意,gitbrute 不会强制使用特定的 SHA1,而只是强制使用前缀(即整个 SHA1 的子部分)。强制使用整个 SHA1(即带有密钥全长的前缀)可能需要“太长时间”。
  • @JanusTroelsen 然后你会添加:/* This line added to avoid collision of the avoid collision line */
【解决方案4】:

我做了一个实验来确切了解 Git 在这种情况下的行为方式。这是版本 2.7.9~rc0+next.20151210(Debian 版本)。我基本上只是通过应用以下差异并重建 git 将哈希大小从 160 位减少到 4 位:

--- git-2.7.0~rc0+next.20151210.orig/block-sha1/sha1.c
+++ git-2.7.0~rc0+next.20151210/block-sha1/sha1.c
@@ -246,6 +246,8 @@ void blk_SHA1_Final(unsigned char hashou
    blk_SHA1_Update(ctx, padlen, 8);

    /* Output hash */
-   for (i = 0; i < 5; i++)
-       put_be32(hashout + i * 4, ctx->H[i]);
+   for (i = 0; i < 1; i++)
+       put_be32(hashout + i * 4, (ctx->H[i] & 0xf000000));
+   for (i = 1; i < 5; i++)
+       put_be32(hashout + i * 4, 0);
 }

然后我做了一些提交并注意到以下内容。

  1. 如果已存在具有相同哈希的 blob,则根本不会收到任何警告。一切似乎都正常,但是当您推送、有人克隆或您还原时,您将丢失最新版本(符合上述说明)。
  2. 如果树对象已经存在并且您使用相同的哈希创建了一个 blob:一切看起来都很正常,直到您尝试推送或有人克隆您的存储库。然后你会看到这个 repo 已经损坏了。
  3. 如果提交对象已经存在并且您使用相同的哈希创建了一个 blob:与 #2 相同 - 损坏
  4. 如果 blob 已经存在并且您使用相同的哈希创建提交对象,则更新“ref”时它将失败。
  5. 如果 blob 已经存在,并且您使用相同的哈希创建树对象。创建提交时会失败。
  6. 如果树对象已经存在并且您使用相同的哈希创建提交对象,则更新“ref”时它将失败。
  7. 如果一个树对象已经存在并且你创建了一个具有相同散列的树对象,那么一切看起来都没有问题。但是当您提交时,所有存储库都会引用错误的树。
  8. 如果一个提交对象已经存在,并且您使用相同的哈希创建一个提交对象,那么一切看起来都正常。但是当您提交时,将永远不会创建提交,并且 HEAD 指针将移动到旧提交。
  9. 如果一个提交对象已经存在并且你用相同的哈希创建一个树对象,创建提交时它会失败。

对于#2,当你运行“git push”时,你通常会得到这样的错误:

error: object 0400000000000000000000000000000000000000 is a tree, not a blob
fatal: bad blob object
error: failed to push some refs to origin

或:

error: unable to read sha1 file of file.txt (0400000000000000000000000000000000000000)

如果你删除文件然后运行“git checkout file.txt”。

对于 #4 和 #6,您通常会收到如下错误:

error: Trying to write non-commit object
f000000000000000000000000000000000000000 to branch refs/heads/master
fatal: cannot update HEAD ref

运行“git commit”时。在这种情况下,您通常只需再次输入“git commit”,因为这将创建一个新的哈希(因为更改了时间戳)

对于 #5 和 #9,您通常会收到如下错误:

fatal: 1000000000000000000000000000000000000000 is not a valid 'tree' object

运行“git commit”时

如果有人试图克隆您损坏的存储库,他们通常会看到如下内容:

git clone (one repo with collided blob,
d000000000000000000000000000000000000000 is commit,
f000000000000000000000000000000000000000 is tree)

Cloning into 'clonedversion'...
done.
error: unable to read sha1 file of s (d000000000000000000000000000000000000000)
error: unable to read sha1 file of tullebukk
(f000000000000000000000000000000000000000)
fatal: unable to checkout working tree
warning: Clone succeeded, but checkout failed.
You can inspect what was checked out with 'git status'
and retry the checkout with 'git checkout -f HEAD'

让我“担心”的是,在两种情况下 (2,3),存储库在没有任何警告的情况下损坏,在 3 种情况下 (1,7,8),一切似乎都正常,但存储库内容与实际情况不同你期望它是。克隆或拉取的人将拥有与您拥有的内容不同的内容。案例 4、5、6 和 9 都可以,因为它会因错误而停止。我想如果它至少在所有情况下都因错误而失败会更好。

【讨论】:

  • 很棒的答案 - 减少哈希大小以查看它的实际行为是一个好主意。
  • @Gnurou 我同意并且当时确实支持该答案。 git 邮件列表中提到了这些案例吗?
  • 另外,如果有的话,有什么计划转移到另一种哈希算法。
  • 必读 - Linus Torval 的解释:plus.google.com/+LinusTorvalds/posts/7tp2gYWQugL
  • @phil_lgr 在 G+ 被关闭时,Linus 的言论在任何地方都有备份吗?
【解决方案5】:

对于像 SHA-1 这样的哈希有几种不同的攻击模型,但通常讨论的一种是碰撞搜索,包括 Marc Stevens 的 HashClash 工具。

"As of 2012, the most efficient attack against SHA-1 is considered to be the one by Marc Stevens[34] with an estimated cost of $2.77M to break a single hash value by renting CPU power from cloud servers."

正如人们指出的那样,您可以强制与 git 发生哈希冲突,但这样做不会覆盖另一个存储库中的现有对象。我想即使git push -f --no-thin 也不会覆盖现有对象,但不是 100% 确定。

也就是说,如果您入侵远程存储库,那么您可以将您的虚假对象设置为旧的那里,可能会将被黑客入侵的代码嵌入到 github 或类似的开源项目中。如果你小心点,也许你可以引入一个新用户下载的破解版本。

然而,我怀疑该项目的开发人员可能会做的许多事情可能会暴露或意外破坏您价值数百万美元的黑客攻击。特别是,如果某些开发人员(您没有破解)在修改受影响的文件后运行上述git push --no-thin,有时甚至没有--no-thin,这将是一大笔钱。

【讨论】:

    【解决方案6】:

    我认为密码学家会庆祝的。

    引用Wikipedia article on SHA-1:

    2005 年 2 月,王晓云、尹伊群、尹丽莎和于洪波的袭击被宣布。 攻击可以在完整版的 SHA-1 中发现冲突,需要少于 2^69 次操作。 (蛮力搜索需要 2^80 次操作。)

    【讨论】:

    • 重点是在 SHA1 中发现了一个缺陷,这大约是在引入 Git 的时候。此外,概率是非线性的。仅仅因为你玩了五十年彩票并不意味着你有更高的中奖机会。你只是每次都有同样的机会。第一次玩的人仍然可以获胜。
    • 这只是发现冲突的攻击,这意味着您可以找到y 这样h(x) == h(y)` 对 SSL 证书等任意数据构成严重威胁,但这不会影响Git 容易受到第二次预映像攻击,这意味着拥有消息 x 您可以将其修改为消息 x'h(x) == h(x')。所以这种攻击不会削弱 Git。出于安全原因,Git 也没有选择 SHA-1。
    • 现在发现了一个冲突——只是还没有直接困扰 git。 stackoverflow.com/questions/42433126/…
    • 2^69 大约是 600 个 Exa 操作。八年后,英伟达用 A100 升级的 SaturnV 超级计算机可以执行 4.6 ExaOPS,因此它可能会在 2 分钟多一点的时间内解决这个问题,或者在几天内进行暴力攻击。
    猜你喜欢
    • 2017-07-14
    • 2017-07-14
    • 2011-10-31
    • 1970-01-01
    • 2012-05-09
    • 2013-06-06
    • 1970-01-01
    • 2011-06-26
    • 2012-03-18
    相关资源
    最近更新 更多