Git如何处理BLOB上的SHA-1冲突?
这可能从来没有发生在现实世界中,可能永远不会发生,但是让我们考虑一下:假设你有一个git仓库,做一个提交,并且非常不走运:其中一个blob最终具有相同的SHA-1作为另一个已经在你的仓库中。 问题是,Git如何处理这个问题? 简单地失败? find一种方法来链接两个斑点,并根据上下文来检查哪一个是必要的?
更多的是一个脑筋急转弯,而不是一个实际的问题,但我觉得这个问题很有趣。
我做了一个实验,以明确Git在这种情况下的performance。 这是版本2.7.9〜rc0 + next.20151210(Debian版本)。 我基本上只是通过应用下面的diff和rebuild 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); }
然后我做了一些提交,并注意到以下内容。
- 如果一个BLOB已经存在相同的散列,你将不会得到任何警告。 一切似乎都没有问题,但是当你推,克隆或者回复时,你会失去最新版本(与上面解释的一致)。
- 如果一个树形对象已经存在,并且你使用相同的散列形成一个blob:一切看起来都是正常的,直到你试图推动或者某人克隆你的仓库。 那么你会看到回购是腐败的。
- 如果一个提交对象已经存在,你使用相同的哈希做一个blob:#2相同 – 腐败
- 如果一个blob已经存在,并且你使用相同的哈希来创build一个提交对象,那么在更新“ref”时会失败。
- 如果一个blob已经存在,并且使用相同的散列创build一个树对象。 创build提交时会失败。
- 如果一个树对象已经存在,并且你使用相同的哈希来创build一个提交对象,那么在更新“ref”时将失败。
- 如果一个树形对象已经存在,并且使用相同的散列形成一个树形对象,那么一切都会好起来的。 但是当你提交时,所有的版本库都会引用错误的树。
- 如果一个提交对象已经存在,并且你使用相同的哈希来提交一个提交对象,那么一切都会好起来的。 但是当你提交的时候,提交将永远不会被创build,HEAD指针将被移动到一个旧提交。
- 如果一个提交对象已经存在,并且使用相同的散列创build一个树对象,那么在创build提交时将失败。
对于#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”,因为这将创build一个新的散列(由于更改的时间戳)
#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),存储库在没有任何警告的情况下变得腐败,在三种情况下(1,7,8),一切似乎都正常,但存储库内容与您期望的不同成为。 克隆或拉动的人将会拥有与你拥有的内容不同的内容。 情况4,5,6和9是好的,因为它会停止的错误。 至less在所有情况下,如果它失败了,我认为会更好。
原始答案(2012)(请参阅下面的shattered.io
2017 SHA1碰撞)
那个来自Linus的(2006年)答案可能还是相关的:
不。 如果它具有相同的SHA1,则意味着当我们从另一端收到对象时,我们不会覆盖已有的对象。
所以会发生的是,如果我们看到一个碰撞,任何特定存储库中的“早期”对象总是会被覆盖。 但是请注意,“更早”显然是每个存储库,因为git对象networking会生成一个不完全有序的DAG,因此,尽pipe不同的存储库会在直接祖先的情况下“早些时候”对象是通过独立而不是直接相关的分支来实现的,两种不同的回购显然可以按照不同的顺序得到两个对象。
然而,从安全的angular度来看,“早些时候会覆盖”是非常想要的:记住git模型是,你应该主要只信任你自己的仓库。
所以如果你做一个“git pull
”,新的传入对象在定义上比你已经拥有的对象更不值得信任,因此,允许一个新的对象replace一个新的对象是错误的。所以你有两个碰撞的情况:
无意中的那种 ,你不知何故是非常不吉利的,而两个文件最终有相同的SHA1。
那么会发生什么呢?当你提交这个文件(或者做一个“git-update-index
”把它移动到索引中,但是还没有提交的时候),新内容的SHA1就会被计算出来,但是因为它匹配一个旧的对象,一个新的对象不会被创build,并提交或索引结束了指向旧的对象 。
你不会立即注意到(因为索引将匹配旧的对象SHA1,这意味着像“git diff
”这样的东西将使用签出的副本),但是如果你曾经做过树级别的diff(或者你做克隆或拉,或强制结帐),你会突然注意到,该文件已经改变了完全不同于你所期望的。
所以你通常会很快注意到这种碰撞。
在相关新闻中,关于无意碰撞怎么办?
首先,让我提醒人们,无意中发生的碰撞事实上真的不太可能,所以我们很可能永远不会在宇宙的全部历史中看到它。
但是如果发生了,那不是世界的末日: 你最可能要做的只是改变稍微碰撞的文件,只要改变内容就强制一个新的提交 (添加一个注释“/* This line added to avoid collision */
“),然后教Git关于魔术SHA1已被certificate是危险的。
因此,在几百万年之后,也许我们将不得不添加一个或两个“有毒”的SHA1值git。 这不太可能是一个维修问题;)攻击者因为有人破坏(或蛮力)SHA1 而发生相撞 。
这显然比不经意的types更可能,但是根据定义,它总是一个“远程”的存储库。 如果攻击者可以访问本地存储库,他会有更简单的方法来解决你的问题。
所以在这种情况下, 碰撞是完全没有问题的 :你会得到一个与攻击者意图不同的“坏”存储库,但是因为你永远不会真正使用他的碰撞对象,所以它和攻击者根本就没有发现碰撞 ,只是使用已经拥有的对象(即100%相当于生成相同SHA1的相同文件的“平凡”冲突)。
定期提到使用SHA-256的问题 ,但现在不采取行动。
注意(幽默):你可以用Brad Fitzpatrick( bradfitz
)的项目gitbrute强制提交一个特定的SHA1 前缀 。
gitbrute蛮力一对作者+提交时间戳,使得产生的git提交具有所需的前缀。
例如: https : //github.com/bradfitz/deadbeef
Daniel Dinnyes 在对7.1 Git Tools – Revision Selection 的评论中指出,其中包括:
更高的可能性是,你的编程团队的每个成员都会在同一天晚上被无关的事件中的狼袭击和杀死。
即使是最近(2017年2月) shattered.io
certificate了造成SHA1碰撞的可能性:
(请参阅我的独立答案 ,包括Linus Torvalds的Google+博客)
- a /仍然需要超过9,223,372,036,854,775,808个SHA1计算。 这个处理能力相当于6,500年的单CPU计算和110年的单GPU计算。
- b /会伪造一个文件(使用相同的SHA1),但是使用额外的约束条件,它的内容和大小会产生相同的SHA1(单独的内容冲突是不够的):请参阅“ 如何计算git哈希值 ” : 基于内容和大小计算blob SHA1 。
有关更多信息,请参阅Valerie Anita Aurora的 “ encryption散列函数的生存期 ”。
她在该页面上指出:
Google花了6500个CPU年和110个GPU年来说服每个人,我们需要停止使用SHA-1作为安全关键应用程序。
也因为它很酷
请参阅下面的单独答案 。
根据Pro Git的说法:
如果您确实碰巧提交了一个哈希值与存储库中以前的对象具有相同SHA-1值的对象,那么Git将会看到您的Git数据库中已有的对象,并假定它已经被写入。 如果您尝试在某个时候再次检查该对象,则将始终获取第一个对象的数据。
所以它不会失败,但它不会保存你的新对象。
我不知道如何在命令行上看,但这肯定会令人困惑。
再往下看,相同的参考文献试图说明这种碰撞的可能性:
下面是一个例子,让您了解如何获得SHA-1碰撞。 如果地球上所有的65亿人都在编程,每一秒钟,每个人都在生产相当于整个Linux内核历史(100万个Git对象)的代码,并将其推入一个巨大的Git存储库,那么需要5年该存储库包含足够的对象,以使单个SHA-1对象冲突的概率为50%。 更高的可能性是,你的编程团队的每个成员都会在同一天晚上被无关的事件中的狼袭击和杀死。
为了补充2012年以前的答案 ,现在(2017年2月,五年后),一个实际的SHA-1与破碎的冲突的例子,你可以制作两个冲突的PDF文件:这就是获得一个SHA-第一个PDF文件上的数字签名也可以被滥用为第二个PDF文件上的有效签名。
另请参见“ 多年来在死亡之门,广泛使用的SHA1function现在已经死亡 ”,以及这个插图 。
2月26日更新:Linus 在Google+信息中确认了以下几点:
(1)首先 – 天空不坠落。 使用encryption哈希来处理安全签名等问题与使用一个为内容寻址系统(如git)生成“内容标识符”有很大的区别。
(2)其次,这种特殊的SHA1攻击的性质意味着实际上很容易减轻攻击,并且已经有两套补丁发布用于缓解。
(3)最后,实际上有一个相当简单的过渡到一些其他的散列,不会打破世界 – 甚至是旧的git仓库。
原来的答案(2月25日)但是:
- 这可以伪造一个blob,但是树的SHA-1仍然会发生变化,因为伪造的blob的大小可能与原来的不一样:请参阅“ 如何计算git hash? 根据内容和大小计算BLOB SHA1 。
它确实有一些git-svn
问题 。 或者更像svn本身 ,如这里所见 。 - 正如我在原来的回答中所提到的那样 ,目前这种尝试的成本仍然很高(6500个CPU年和100个GPU年)。另请参见Valerie Anita Aurora的“ 密码散列函数的生命周期 ”。
- 正如之前评论的,这不是关于安全或信任,而是数据完整性(重复数据删除和错误检测),可以很容易地被
git fsck
检测到,如今天的Linus Torvalds所提到的 。git fsck
会警告一个提交消息,隐藏在NUL
之后的不透明数据(尽pipeNUL
并不总是存在于欺诈文件中 )。
不是每个人都打开transfer.fsck
,但GitHub确实:在格式错误的对象或链接断开的情况下,任何推送都将中止。 虽然…有一个原因,这是不是默认激活 。 - 一个PDF文件可以有任意的二进制数据,你可以改变生成一个冲突的SHA-1,而不是伪造的源代码。
使用相同的头提交散列和不同的内容创build两个Git存储库的实际问题。 即使如此,这次袭击依然令人费解 。 - Linus补充说 :
供应链pipe理的重点在于它不是关于一次性事件,而是关于连续的历史。 这也从根本上意味着成功的攻击需要随着时间的推移而工作,而不是被察觉的。
如果你能骗一次SCM,插入你的代码,并在下周被发现,你实际上没有做任何有用的事情。 你只烧自己。
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作为select的前缀,并
- 使用相同前缀碰撞攻击来生成碰撞的好/坏对象。
- …这是无用的,因为好的和坏的提交对象仍然指向同一棵树!
此外,您已经可以检测到每个文件中存在的SHA-1的密码分析冲突攻击,并使用cr-marcstevens/sha1collisiondetection
在Git中添加一个类似的检查会有一些计算成本 。
关于更改散列, Linux评论 :
散列的大小和散列algorithm的select是独立的问题。
你可能会做的是切换到一个256位散列,在内部和本地git数据库中使用,然后默认情况下只显示散列为40个字符的hexstring(有点像我们已经如何缩写的东西很多情况下)。
这样,围绕git的工具甚至不会看到变化,除非传递给一些特殊的“--full-hash
”参数(或“--abbrev=64
”或其他 – 默认是我们缩写为40)。
尽pipe如此,一个转换计划(从SHA1到另一个散列函数)仍然是复杂的 ,但是正在进行积极的研究。
convert-to-object_id
广告系列 正在进行中 :
3月20日更新: GitHub详细描述了可能的攻击及其保护 :
SHA-1名称可以通过各种机制分配信任。 例如,Git允许您encryption签名提交或标记。 这样做只签署提交或标记对象本身,而后者又通过使用其SHA-1名称指向包含实际文件数据的其他对象。 这些对象之间的碰撞可能会产生一个看起来有效的签名,但是它指向的数据不同于签名者的意图。 在这样的攻击中,签名者只看到一半的碰撞,受害者看到另一半。
保护:
最近的攻击使用了特殊的技术来利用SHA-1algorithm中的弱点,在更短的时间内find冲突。 这些技术在计算碰撞对的一半的SHA-1时可以检测的字节中留下一个模式。
GitHub.com现在对其计算的每个SHA-1执行此检测,如果有证据表明该对象是碰撞对的一半,则中止操作。 这样可以防止攻击者使用GitHub来说服一个项目接受“无辜的”一半的碰撞,并阻止他们托pipe恶意的一半。
请参阅Marc Stevens的 “ sha1collisiondetection
”
我认为密码学家会庆祝。
维基百科的文章引用SHA-1 :
2005年2月,王晓云,益群丽莎,余洪波发动袭击事件。 攻击可以在完整版本的SHA-1中发现冲突,需要less于2 ^ 69次操作。 (蛮力search需要2 ^ 80个操作。)
对SHA-1等哈希有几种不同的攻击模式,但通常讨论的是碰撞search,包括Marc Stevens的HashClash工具。
“截至2012年,对SHA-1的最有效的攻击被Marc Stevens [34]认为是最有效的攻击,估计成本为277万美元,通过租用云服务器的CPU电力来打破一个散列值。
正如人们指出的,你可以强制与git的散列冲突,但这样做不会覆盖另一个存储库中的现有对象。 我想甚至git push -f --no-thin
不会覆盖现有的对象,但不是100%确定的。
也就是说,如果你入侵一个远程仓库,那么你可以让你的虚假对象是那个较旧的对象,可能将被黑客入侵的代码embedded到github或类似的开源项目中。 如果你小心,那么也许你可以引入新用户下载的黑客版本。
不过,我怀疑项目开发人员可能做的很多事情可能会暴露或意外破坏您的数百万美元的黑客攻击。 尤其是,如果一些开发者(你没有入侵过的)在修改受影响的文件之后运行前面提到的git push --no-thin
,有时甚至没有--no-thin
依赖,那么这是一笔巨大的资金。