Git如何处理Blob上的SHA-1冲突?


543

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

比实际的问题更有趣,但我发现这个问题很有趣。


76
曾经是一个脑筋急转弯,现在可能是一个实际问题
Toby

11
@Toby这个问题是关于图像前攻击的;Google展示的是碰撞攻击 -类似但略有不同。您可以在此处了解更多有关差异的信息
Saheed

@Saheed我看不到这个问题的哪一部分是关于映像前攻击的,因为提出的问题只是关于git存储库中冲突,而不是利用它。
Toby

3
@Toby最初的脑筋急转弯不是关于攻击(既不是预成像也不是碰撞),而是关于意外的碰撞,这种意外的碰撞是不可能的,因此不值得考虑。我认为Saheed正确地试图说这仍然不是一个实际的问题。但是,根据Git的使用方式,Google碰撞攻击可能会造成安全问题,这是正确的。
安德鲁·菲利普斯

Answers:


735

我做了一个实验,以找出Git在这种情况下的确切表现。版本2.7.9〜rc0 + next.20151210(Debian版本)。我基本上只是通过应用以下diff并重建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:一切似乎都很正常,直到您尝试推送或有人克隆您的存储库为止。然后,您将看到存储库已损坏。
  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可以,因为它会因错误而停止。我想如果它至少在所有情况下都因错误而失败,那就更好了。


156
很棒的答案-减小哈希大小以查看其实际行为是一个好主意。
Gnurou '16

4
@Gnurou我同意,并在当时回答了这个问题。git邮件列表中是否提到了这些案例?
VonC

1
在不减小哈希值的情况下发生这种情况的可能性有多大?
Mathias Bader

4
另外,有什么计划转移到另一个哈希算法。
皮特

9
必须阅读
-Linus Torval

238

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

从莱纳斯旧(2006年)的回答仍然可能是相关的:

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

因此,发生的事情是,如果我们遇到冲突,则任何特定存储库中的“较早”对象将始终被覆盖。但请注意,“较早版本”显然是每个存储库,因为git对象网络生成的DAG并非完全有序,因此在直接祖先的情况下,如果不同的存储库会就“较早版本”达成一致,如果一个对象是通过不同的分支而不是直接相关的分支而来的,两个不同的存储库显然可以按不同的顺序获得这两个对象。

但是,从安全的角度来看,“早期将被覆盖”是您想要的:记住git模型是您应该主要只信任自己的存储库。
因此,如果您执行“ git pull”,则从定义上讲,新传入的对象不如现有对象具有更高的信任度,因此,允许新对象替换旧对象是错误的。

因此,您有两种情况发生碰撞:

  • 不经意的那种,你不知何故是非常非常不走运,而两个文件最终具有相同SHA1。
    那时发生的事情是,当您提交该文件(或执行“ git-update-index”将其移入索引但尚未提交)时,将计算新内容的SHA1,但由于它与旧对象匹配,不会创建一个新对象,并且commit-or-index最终指向对象
    您不会立即注意到(因为索引将与旧对象SHA1相匹配,这意味着类似“ git diff”将使用检出的副本),但是如果您执行树级比较(或执行克隆)或拉出或强制执行结帐),您会突然发现该文件已更改为某种内容完全不同于您的预期。
    因此,您通常会很快注意到这种碰撞。
    在相关新闻中,问题是如何处理无意中的碰撞。
    首先,让我提醒人们,无意中的碰撞真的非常不可能,所以我们很可能永远都不会看到它。宇宙
    但是,如果发生这种情况,那还不是世界末日:您最有可能要做的就是更改发生轻微冲突的文件,然后使用更改后的内容强制执行新提交(添加注释,表示“ /* This line added to avoid collision */”),然后向git讲解已证明是危险的魔术SHA1。
    因此,在几百万年的时间里,也许我们必须向git中添加一个或两个“中毒”的SHA1值。这不太可能是维护问题;)

  • 攻击者那种碰撞,因为有人打破(或野蛮强制)SHA1。
    这一个显然是一个很多比无意样的可能性较大,但顾名思义它总是一个“远程”库。如果攻击者可以访问本地存储库,那么他将有更简单的方法来欺骗您。
    所以在这种情况下,碰撞是完全不成问题:你会得到一个“坏”的库是从什么攻击者预想的不同,但因为你从来没有真正用他的碰撞物体,这是字面上没有什么不同攻击者根本没有发现碰撞,但仅使用您已有的对象即可(即,它等同于生成相同SHA1的相同文件的“平凡”冲突)。

经常提到使用SHA-256问题,但目前尚未采取行动(2012年)。
注意:从2018和Git 2.19开始,代码被重构为使用SHA-256。


注(幽默):你可以强制提交到一个特定的SHA1 前缀,项目gitbrute布拉德·菲茨帕特里克(bradfitz

gitbrute brute强制使用一对author + commititter时间戳,以使生成的git commit具有所需的前缀。

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


丹尼尔Dinnyes指出,在评论中,以7.1的Git工具-修正选择,其中包括:

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


即使是最近(二月2017)shattered.io证明伪造SHA1碰撞的可能性:
(见得多了,我单独的答案,包括Linus Torvalds的Google+信息)

  • a /仍然需要超过9,223,372,036,854,775,808 SHA1计算。这相当于6500年的单CPU计算能力和110年的单GPU计算能力。
  • b /将伪造一个文件(具有相同的SHA1),但是在附加约束下,其内容大小将产生相同的SHA1(仅对内容的冲突是不够的):请参阅“ 如何计算git hash? ”) :根据内容大小计算斑点SHA1

有关更多信息,请参见Valerie Anita Aurora的加密哈希函数的生存期 ” 。 她在该页面中指出:

Google花了6500年的CPU时间和110年的GPU时间说服每个人,我们都需要停止对安全关键型应用程序使用SHA-1。
还因为它很酷

下面的单独答案中查看更多内容。


25
扭曲:添加后/* This line added to avoid collision */:D 仍然可以散列相同:D可以赢得两次彩票:P
Janus Troelsen

4
@JanusTroelsen当然可以,但这仍然是彩票,不是吗?;)(如关于SHA1的简短说明中所述
VonC

6
@VonC关于该参考文献:是全球狼人流行的爆发-消灭了全人类,并导致我的所有开发人员在同一晚死亡,即使他们分布在地理位置上也是如此-被视为无关事件?当然,显然,假设它发生在满月。现在,这种情况将改变事情。甚至想到这都是精神错乱!那是完全不同的概率尺度!那意味着我们必须... 停止使用GIT!现在!!!所有人RUUUUUN !!!!!!!
Daniel Dinnyes 2014年

2
请注意,gitbrute不会强制使用特定的SHA1,而只会强制使用前缀(即整个SHA1的子部分)。强制整个SHA1(即带有密钥全长的前缀)可能会花费“太长”的时间。
2015年

2
@JanusTroelsen然后,您将添加:/* This line added to avoid collision of the avoid collision line */
smg

42

根据Pro Git

如果您确实提交了一个哈希值与存储库中先前对象相同的SHA-1值的对象,则Git将在您的Git数据库中看到该先前对象,并假定它已被写入。如果您尝试在某个时刻再次检出该对象,则将始终获得第一个对象的数据。

因此它不会失败,但也不会保存新对象。
我不知道在命令行上会怎样,但这肯定会造成混乱。

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

这是一个示例,可让您大致了解发生SHA-1冲突的情况。如果地球上所有的65亿人都在编程,并且每一秒钟产生的代码都相当于整个Linux内核历史记录(100万个Git对象)并将其推入一个巨大的Git存储库,则需要5年的时间该存储库包含足够多的对象,因此一次SHA-1对象冲突的可能性为50%。更有可能的是,您的编程团队的每个成员都将在同一晚的不相关事件中被狼袭击并杀死。


44
我想看看最后一句话的数字来源;-)
Joachim Sauer

17
@Jasper:该链接是很好的文档,但没有包含有关团队的每个成员在同一晚发生无关事件中被狼袭击和杀死的概率的统计信息。
约阿希姆·绍尔

5
@贾斯珀:嗯,按照我的阅读方式,该文字从字面上声称,当晚有65亿团队成员被狼杀死的概率高于50%。但是我的主要反对他的说法是,这样的事件就必须 成为一个世界性的现象; 这是不可思议的,这可能会出现由于不相关的事件。;)
Keith Robertson

5
@KeithRobertson我很确定,相比世界上每个人都在产生疯狂数量的代码的情况下,所有实际团队成员都被吃掉的机会与哈希冲突的机会相比,以及在这种情况下花费的时间发生碰撞的机率高达50%(即,狼事件并未涉及整个世界,而50%的事件与狼分开了)。但是,如果确实发生了这样的事件,您确实明白了这一点,那么git hash冲突也应该如此。(当然,一个(几乎)纯粹是基于偶然性,而另一个则不是,但仍然如此。)
Jasper


23

作为我2012年以前的回答,现在(五年后的2017年2月)是一个实际的SHA-1与shattered.io碰撞的示例,您可以在其中制作两个碰撞的PDF文件:即获得SHA-第一个PDF文件上的1个数字签名,也可以被滥用为第二个PDF文件上的有效签名。
另请参见“ 多年以来,广泛使用的SHA1功能已经失效 ”和此图

2月26日更新:Linus 在Google+帖子中确认了以下几点:

(1)首先-天空没有落下。将加密哈希用于安全签名之类的代码与为内容可寻址系统(如git)生成“内容标识符”的代码之间存在很大的区别。

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

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

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

从Git 2.19(Q3 2018)开始,Git选择SHA-256作为NewHash,并且正在将其集成到代码中(这意味着SHA1仍然是默认值(Q2 2019,Git 2.21),但是SHA2将是继任者)


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

乔伊·赫斯(Joey Hess)Git仓库中尝试使用这些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

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

因此,主要的攻击媒介(伪造一个提交)将是

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

另外,您已经可以通过以下方式检测到针对每个文件中存在的SHA-1的密码分析冲突攻击: cr-marcstevens/sha1collisiondetection

在Git本身中添加类似的检查会带来一定的计算成本

关于更改哈希,Linux注释

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

但是,过渡计划(从SHA1到另一个哈希函数)过渡计划仍然很复杂,但仍在积极研究中。
一个convert-to-object_id活动正在进行中


3月20日更新:GitHub详细介绍了可能的攻击及其保护措施

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

保护:

最近的攻击使用特殊技术来利用SHA-1算法中的弱点,这些弱点可以在更短的时间内找到冲突。这些技术在字节中留下了一个模式,当计算冲突对中任一对的SHA-1时可以检测到。

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

参见马克·史蒂文斯sha1collisiondetectionMarc Stevens)的 “ ”


再次,随着2018年第一季度Git 2.16添加了表示哈希算法的结构,开始向新哈希过渡的实现。
如上所述,新的受支持的哈希将为SHA-256


碰撞:1.试图造成碰撞,并非偶然发生。2.从PDF报告中:总共花费的计算量相当于2 ^ 63.1 SHA-1压缩,大约花费了6,500 CPU年和100 GPU年3.尽管我们应该从MD5和SHA-1继续前进,但是一般来说对于文件唯一用法来说是可以的。
zaph

值得注意的是,WebKit检入了冲突的PDF以进行测试。它破坏了他们的git-svn镜像基础结构:bugs.webkit.org/show_bug.cgi?id=168774#c24
dahlbyk

1
@dahlbyk确实值得注意……因为我在回答中指出了这一点(git-svn尽管“虽然确实有一些问题”背后的链接是间接提及的)
VonC

1
@Mr_and_Mrs_D否,它仍然不会因错误而失败。一个重要的补丁正在进行中,这将有助于促进冲突检测:marc.info/?l=git&m=148987267504882&w=2
VonC

1
@Mr_and_Mrs_D SEe在stackoverflow.com/posts/42450327/revisions中编辑4 :现在确实失败了,至少在升级到GitHub时失败了。
VonC

6

我认为密码学家会庆祝。

引用SHA-1上的Wikipedia文章

2005年2月,宣布了王晓云,尹奕群和于洪波的攻击。攻击可以在完整版本的SHA-1中发现冲突,所需操作少于2 ^ 69。(强行搜索将需要2 ^ 80次操作。)


7
关键是在SHA1中发现了一个缺陷,大约是在引入Git的时候。同样,概率是非线性的。仅仅因为您玩彩票已有50年,并不意味着您有更大的中奖机会。您每次都有相同的机会。第一次玩的人仍然可以赢。
0xC0000022L 2014年

这是唯一的攻击,发现碰撞,这意味着,你可以找到y,使得h(x) == H(Y)`这对于像SSL证书任意数据严重威胁然而,这并不影响混帐这将是脆弱的第二原像攻击这意味着,有消息x你可以修改它的消息x'h(x) == h(x')。因此,这种攻击不会削弱Git。而且出于安全原因,Git尚未选择SHA-1。
Hauleth '17

现在发现了一个碰撞-尚未直接碰撞git。 stackoverflow.com/questions/42433126/...
威廉Hengeveld

2 ^ 69大约是600 Exa操作。八年后,英伟达的A100升级后的SaturnV超级计算机可以执行4.6 ExaOPS,因此有可能在2分钟内解决此问题,或者在几天内进行蛮力攻击。
qdin

6

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

“截至2012年,对SHA-1的最有效攻击被Marc Stevens [34]认为,通过从云服务器租用CPU功能来破坏单个哈希值的估计费用为277万美元。”

正如人们指出的那样,您可以使用git强制进行哈希冲突,但这样做不会覆盖另一个存储库中的现有对象。我以为甚至git push -f --no-thin不会覆盖现有的对象,但不能100%确定。

就是说,如果您入侵远程存储库,则可以使假对象成为那里的旧对象,可能将被入侵的代码嵌入到github或类似的开源项目中。如果您小心的话,也许可以介绍一个新用户下载的黑客版本。

但是我怀疑该项目的开发人员可能会做很多事情,从而暴露或意外破坏您的数百万美元黑客。特别是,如果某个您没有黑客的开发人员git push --no-thin在修改了受影响的文件之后运行了上述程序,这甚至是一笔巨款,有时甚至没有--no-thin依赖。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.