这种情况可能在现实世界中从未发生过,并且可能永远不会发生,但让我们考虑一下:假设您有一个git存储库,在其中进行了一次提交,然后非常不幸地发现其中一个blob的SHA-1与已经存在于您的存储库中的另一个blob相同。问题是,Git会如何处理这种情况?会简单地失败吗?会找到一种方式将两个blob关联起来并根据上下文检查哪一个是需要的吗?
这更像是一个脑筋急转弯而不是一个真正的问题,但我认为这个问题很有趣。
这种情况可能在现实世界中从未发生过,并且可能永远不会发生,但让我们考虑一下:假设您有一个git存储库,在其中进行了一次提交,然后非常不幸地发现其中一个blob的SHA-1与已经存在于您的存储库中的另一个blob相同。问题是,Git会如何处理这种情况?会简单地失败吗?会找到一种方式将两个blob关联起来并根据上下文检查哪一个是需要的吗?
这更像是一个脑筋急转弯而不是一个真正的问题,但我认为这个问题很有趣。
我进行了一项实验,以确定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);
}
然后我做了一些提交并注意到以下内容:
对于#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)中,存储库会在没有任何警告的情况下变得损坏。而在另外三种情况(1、7、8),看起来一切正常,但存储库内容与预期不同。克隆或拉取的人将得到与您不同的内容。第4、5、6和9种情况是可以的,因为它们将停止并显示一个错误。我认为如果在所有情况下至少出现错误会更好。
原始答案(2012)(请参见下面的shattered.io
2017 SHA1碰撞):
Linus于2006年的旧答案可能仍然相关:
如果两个对象有相同的SHA1值,那么当我们从另一端接收到这个对象时,就不会覆盖已经存在的对象。因此,如果我们发现碰撞,每个特定存储库中的“早期”对象将始终被覆盖。但请注意,“更早期”的定义显然是针对每个存储库的,因为git对象网络生成的DAG没有完全排序,因此虽然不同的仓库在直系祖先情况下会达成共识,但如果这个对象通过不相关分支进行,则两个不同的仓库可能会以不同的顺序获取两个对象。使用SHA-256的问题经常被提及,但目前尚未实施(2012年)。
注意:从2018年和Git 2.19开始, 代码正在重构以使用SHA-256。
注意(幽默):您可以使用Brad Fitzpatrick (bradfitz
)的项目gitbrute,强制将提交推送到特定的SHA1前缀。
gitbrute会暴力破解作者和提交者时间戳的组合,以便生成具有所需前缀的git提交。
示例:https://github.com/bradfitz/deadbeef
Daniel Dinnyes在评论中指出7.1 Git 工具 - 版本选择,其中包括:
你的编程团队中每个成员在同一天晚上都有可能遭受不相关事件中袭击和死亡。
请查看我下面的单独的答案以了解更多信息。Google花费了6500个CPU年和110个GPU年来说服所有人停止使用SHA-1进行安全关键应用。
同样也因为它很酷
/* 此行用于避免避撞行与其他代码的冲突 */
- smg在我2012年的答案(链接)之后,现在已经有一个实际的SHA-1碰撞例子了(2017年2月,五年后),可以在shattered.io上制作两个相撞的PDF文件:也就是说,在第一个PDF文件上获得SHA-1数字签名,同时该签名也可以被滥用为第二个PDF文件的有效签名。
另请参阅“多年来濒于崩溃的广泛使用的SHA1功能现已死亡”和此插图。
2月26日更新:Linus在Google+帖子中确认以下几点(链接):
(1) 首先,天空并没有塌下来。在安全签名等方面使用加密哈希和在像Git这样的内容可寻址系统中生成“内容标识符”之间有很大的区别。原始答案(2月25日)
git-svn
来说确实存在一些问题。或者更确切地说,是svn本身存在问题,如此处所示。git fsck
轻松检测出来,如今天Linus Torvalds所说的那样。git fsck
会警告有一个带有NUL
之后隐藏的不透明数据的提交消息(尽管欺诈文件中并不总是存在NUL)。transfer.fsck
,但GitHub会:如果出现格式不正确的对象或损坏的链接,任何推送都将被中止。尽管...这并不是默认激活的原因。SCM的整个目的就是它不是关于一次性事件,而是关于持续的历史。这也从根本上意味着,成功的攻击需要随时间而变化,并且不能被检测到。
如果您可以欺骗SCM一次,插入您的代码,然后在下周被检测到,那么您实际上没有做任何有用的事情。您只是自己烧掉了。
Joey Hess 在 一个 Git 仓库 中尝试了这些 pdf 文件,他发现:
其中包括两个 SHA 和大小相同的文件,但由于 git 在内容前面添加头部的方式,它们得到了不同的 blob。
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 comments:
哈希表的大小和哈希算法的选择是独立的问题。
你可能会切换到256位哈希值,在Git数据库中使用它,在默认情况下只显示一个40个字符的十六进制字符串(就像我们在许多情况下已经缩写了一样)。
这样,Git周围的工具甚至不会看到更改,除非传递了一些特殊的“--full-hash
”参数(或“--abbrev=64
”或其他 - 默认情况下我们将缩写为40个字符)。
尽管如此,从SHA1转换到其他哈希函数的过渡计划仍然很复杂,但正在积极研究中。
一个convert-to-object_id
的活动正在进行中:
3月20日更新:GitHub详细说明了一种可能的攻击及其保护措施:
可以通过各种机制来分配对SHA-1名称的信任。例如,Git允许您为提交或标签进行加密签名。这样做只会对提交或标签对象本身进行签名,而这些对象则使用它们的SHA-1名称指向包含实际文件数据的其他对象。在这些对象中发生碰撞可能会产生一个看起来有效的签名,但它指向的数据与签署者想要的数据不同。在这样的攻击中,签署者只能看到碰撞的一半,而受害人则看到另一半。
保护措施:
最近的攻击使用特殊技术利用SHA-1算法中的弱点,在更短的时间内找到碰撞。这些技术会在字节中留下一种模式,当计算碰撞对的任一半的SHA-1时可以检测到这种模式。 GitHub.com现在对其计算的每个SHA-1进行此检测,并且如果有证据表明该对象是碰撞对的一半,则会中止操作。这样阻止了攻击者利用GitHub说服项目接受他们碰撞的“无辜”一半,同时也阻止他们托管恶意的一半。 请参见Marc Stevens的 "sha1collisiondetection
"。
对于像SHA-1这样的哈希函数,有几种不同的攻击模式,但通常讨论的是碰撞搜索,包括Marc Stevens的HashClash工具。
“截至2012年,最有效的攻击SHA-1的方法被认为是由Marc Stevens提出的一种方法,估计需要花费$2.77M租用云服务器的CPU算力才能破解一个哈希值。”
正如大家指出的,您可以通过git强制发生哈希碰撞,但这样做并不会覆盖另一个存储库中的现有对象。我想即使使用git push -f --no-thin
也无法覆盖现有的对象,但并不确定。
话虽如此,如果您成功入侵了远程存储库,那么您可以在那里将您的伪造对象变成较旧的版本,并可能将黑客代码嵌入到GitHub或类似网站上的开源项目中。如果您小心谨慎,也许可以引入一个被黑客攻击过的版本供新用户下载。
然而,我怀疑项目开发人员可能会做很多事情,这些事情可能会暴露或意外摧毁您的数百万美元的黑客攻击。特别是如果某个未被您攻击的开发人员修改了受影响的文件后运行上述git push --no-thin
甚至没有使用--no-thin
,那么你投入的大量资金可能就打了水漂。
在2005年2月,王小云、尹丽萨和余红波宣布了一次攻击。该攻击可以在SHA-1的完整版本中找到碰撞,需要少于2^69个操作。(暴力搜索需要2^80个操作。)
y
使得 h(x) == h(y)
,这对于任意数据(如 SSL 证书)是严重威胁,但这不会影响 Git,因为它容易受到第二前像攻击的攻击,这意味着如果有消息 x
,你可以将其修改为消息 x'
,使得 h(x) == h(x')
。因此,这种攻击并不会削弱 Git 的安全性。此外,Git 并没有选择 SHA-1 作为安全原因。 - Hauleth