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

时间:2012-02-22 09:42:06

标签: git hash-collision

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

更多的是脑筋急转弯而非实际问题,但我发现这个问题很有趣。

6 个答案:

答案 0 :(得分:677)

我做了一个实验,以确切了解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:一切看起来都很正常,直到您尝试推送或某人克隆您的存储库。然后你会看到回购公司已经腐败了。
  3. 如果提交对象已存在且您使用相同的哈希创建一个blob:与#2相同 - 损坏
  4. 如果blob已经存在且您使用相同的哈希创建提交对象,则在更新“ref”时它将失败。
  5. 如果blob已经存在,并且您使用相同的哈希创建了一个树对象。创建提交时会失败。
  6. 如果树对象已经存在并且您使用相同的哈希创建提交对象,则在更新“ref”时它将失败。
  7. 如果树对象已经存在并且您使用相同的哈希创建了一个树对象,那么一切都会好起来的。但是当你提交时,所有的存储库都会引用错误的树。
  8. 如果提交对象已经存在并且您使用相同的哈希创建提交对象,那么一切似乎都可以。但是当你提交时,永远不会创建提交,并且HEAD指针将被移动到旧提交。
  9. 如果提交对象已经存在并且您使用相同的哈希创建树对象,则在创建提交时它将失败。
  10. 对于#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都可以,因为它会因错误而停止。我认为如果它至少在所有情况下都失败了会更好。

答案 1 :(得分:237)

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

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

  

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

     

所以会发生的是,如果我们看到碰撞,那么早先&#34;任何特定存储库中的对象总是会覆盖。但请注意&#34;早期&#34;显然是每个存储库,在某种意义上,git对象网络生成的DAG不是完全有序的,所以不同的存储库会同意什么是早期&#34;在直接祖先的情况下,如果对象来自单独的而不是直接相关的分支,则两个不同的回购可能显然已经以不同的顺序获得这两个对象。

     

但是,之前的&#34;会覆盖&#34;从安全的角度来看,你非常想要的是:记住git模型是你应该主要只信任你的自己的存储库。
  因此,如果您执行&#34; git pull&#34;,根据定义,新传入对象的可信度低于您已有的对象,因此允许新对象是错误的   替换旧的。

     

所以你有两种碰撞案例:

     
      
  • 无意中,你不知何故非常不走运,两个文件最终拥有相同的SHA1。
      此时,当您提交该文件(或执行&#34; git-update-index&#34;将其移入索引但尚未提交)时,会发生新内容的SHA1被计算,但是因为它匹配旧对象,所以不会创建新对象,并且commit-or-index最终指向对象
      您不会立即注意到(因为索引将与旧对象SHA1匹配,这意味着类似&#34; git diff&#34;会使用签出的副本),但如果您你做过树级别的差异(或者你做克隆或拉动,或者强行结账)你会突然注意到该文件已经变成完全与你预期的不同。<登记/>   所以你通常会很快注意到这种碰撞   在相关新闻中,问题是如何处理无意碰撞..
      首先,让我提醒人们,无意中的碰撞实际上真的不太可能,所以我们很可能永远不会在宇宙的整个历史中看到它。
      但如果它发生了,它不是世界末日:你最有可能做的只是改变稍微相撞的文件,而只是强迫带有更改内容的新提交(添加评论说&#34; /* This line added to avoid collision */&#34;)然后教git关于已被证明是危险的魔法SHA1。
      因此,在过去的几百万年中,我们可能需要添加一两个“中毒”#34; SHA1值为git。它不太可能成为维护问题;)

  •   
  • 攻击者发生碰撞,因为有人破坏(或暴力强迫)SHA1。   这个显然比无意的更容易很多,但根据定义,它总是一个&#34;远程&#34;库。如果攻击者可以访问本地存储库,那么他就有更容易的方法来搞砸你   所以在这种情况下,碰撞完全不是问题:你会得到一个&#34;坏&#34;存储库与攻击者的意图不同,但是因为您从未实际使用过他的碰撞对象,所以 字面上与攻击者没有找到完全碰撞,但只使用你已经拥有的对象(即它100%等同于&#34;平凡&#34;生成相同SHA1的相同文件的碰撞)。

  •   

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


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

  

gitbrute强制执行一对作者+提交者时间戳,以便生成的git commit具有您想要的前缀。

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


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

  

在同一天晚上,你的编程团队的每个成员都会被无关紧要的事件中的狼群攻击并杀死。


即使是最近(2017年2月)shattered.io也证明了伪造SHA1碰撞的可能性:
(请参阅 separate answer 中的更多内容,包括Linus Torvalds&#39; Google+帖子)

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

参见&#34; Lifetimes of cryptographic hash functions&#34;来自 Valerie Anita Aurora 了解更多信息 在那页中,她注意到:

  

Google花了6500个CPU年和110年GPU来说服每个人我们需要停止使用SHA-1来处理安全关键应用程序。
  还因为它很酷

在我的 separate answer below 中查看更多内容。

答案 2 :(得分:41)

根据Pro Git

  

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

所以它不会失败,但它也不会保存你的新物体。
我不知道命令行会怎么样,但这肯定会令人困惑。

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

  

这是一个示例,让您了解如何获得SHA-1冲突。如果地球上所有65亿人都在进行编程,而每一秒,每一个人都生成的代码相当于整个Linux内核历史(100万Git对​​象)并将其推入一个巨大的Git存储库,则需要5年时间该存储库包含足够的对象,以使单个SHA-1对象发生碰撞的概率为50%。你的编程团队的每个成员都会在同一天晚上被无关紧要的事件中的狼群攻击并杀死。

答案 3 :(得分:22)

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

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

  

(1)首先 - 天空不会下降。使用加密哈希来进行安全签名,并使用一个用于生成内容标识符&#34;之间的差别很大。对于像git这样的内容可寻址系统。

     

(2)其次,这种特殊SHA1攻击的性质意味着它实际上很容易被缓解,而且已经发布了两套针对该缓解的补丁。

     

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

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

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

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

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

  

包括两个具有相同SHA和大小的文件,它们确实得到了   不同的blob感谢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的工具甚至不会看到变化,除非传入   一些特别的&#34; --full-hash&#34;论证(或&#34; --abbrev=64&#34;或其他 -   默认是我们缩写为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来说服项目接受“无辜的”#34;他们碰撞的一半,以及阻止他们托管恶意的一半。

参见&#34; sha1collisiondetection&#34; Marc Stevens

同样,在Q1 2018 Git 2.16添加表示散列算法的结构的情况下,已开始实现向新散列的转换。
如上所述,新支持的Hash将为SHA-256

答案 4 :(得分:6)

我认为密码学家会庆祝。

引自Wikipedia article on SHA-1

  

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

答案 5 :(得分: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依赖,也会耗费大量资金。