为什么Git不使用更现代的SHA?

102

我读到Git使用SHA-1摘要作为修订版的ID。为什么它不使用更现代的版本的SHA?


3
性能是我能想到的唯一原因,SHA-1比SHA-2更快。个人认为这是一个糟糕的决定,因为SHA-1的碰撞抗性相对较弱。 - CodesInChaos
5
这篇文章讨论了如果两个不同的文件具有相同的 SHA-1 哈希值,Git 会如何处理。即使 SHA-1 与其他哈希算法相比,已经被证明更容易出现冲突,但是出现这种情况的可能性仍然非常低。如果 Git 确实遇到了这样的冲突,它将无法区分这两个文件,并且可能会导致数据损坏或丢失。然而,由于发生这种情况的可能性很小,因此对大多数 Git 用户来说,这不应该是一个特别大的问题。 - softwariness
9
这个问题在2006年的Git邮件列表中讨论过。请参见整个主题。总之,当时Linus表示,SHA-1只需要足够唯一,以避免发生碰撞,而它也确实做到了这一点。SHA-1并不是Git的安全特性。“任何盲目地接受来自不受信任来源数据的人,在如此多其他方面都会惨遭失败,因此哈希攻击根本不在我们的危机清单上。”-- Linus - tbc0
32
更新:SHA-1碰撞已经在野外出现 https://shattered.it/ - drewr
3
在2018年第三季度,随着Git 2.19的推出,决定选用SHA-256。请参见我下面编辑的答案 - VonC
显示剩余3条评论
4个回答

73

为什么不使用更现代的SHA版本?

2017年12月:将会使用更现代的版本。Git 2.16(2018年第一季度)是首个展示和实现此意图的版本。

注意:请参阅下文的Git 2.19:它将采用 SHA-256

Git 2.16将提供一个基础设施来定义Git中使用的哈希函数,并开始努力将其穿透到各种代码路径中。

查看 提交 c250e02 (2017年11月28日) 由 Ramsay Jones (``) 执行。
查看 提交 eb0ccfd, 提交 78a6766, 提交 f50e766, 提交 abade65 (2017年11月12日) 由 brian m. carlson(bk2204)执行。
(由Junio C Hamano -- gitster --合并于提交721cc43, 2017年12月13日)


添加代表哈希算法的结构

自从我们将来想要支持另一种哈希算法,因此添加表示哈希算法及其所有相关数据的结构
添加一个常量以便轻松枚举哈希算法
实现函数typedefs以创建可用于任何哈希算法的抽象API,并为符合此API的现有SHA1函数提供包装器。

公开十六进制大小和二进制大小的值
虽然其中一个始终是另一个的两倍,但这两个值在整个代码库中都被广泛使用,同时提供这两个值可以提高可读性。

不要在哈希算法结构中包含空对象ID的条目。
由于该值全部为零,因此可以使用适当大小的全零对象ID,并且无需在每个哈希基础上存储给定的对象ID。

当前的哈希函数过渡计划设想了一个时间,我们将接受用户可能以SHA-1或NewHash格式输入的内容。
由于我们无法知道用户提供的是哪一个,因此添加一个表示未知算法的常量以允许我们指示我们必须查找正确的值。


将哈希算法支持与仓库设置集成

在Git的未来版本中,我们计划支持额外的哈希算法。
将哈希算法的枚举与仓库设置集成,并在struct repository中存储指向已枚举数据的指针
当然,我们目前仅支持SHA-1,因此在read_repository_format中硬编码此值
在未来,我们将从配置中枚举此值。
添加一个常量,the_hash_algo,它指向全局仓库中的hash_algo结构指针。
请注意,这是用于将数据序列化到磁盘的哈希,而不是用于向用户显示项目的哈希。
过渡计划预计这些可能不同。
我们可以在未来添加另一个元素(例如ui_hash_algo)来处理这种情况。

2018年8月更新,对于Git 2.19(2018年第三季度),Git似乎选择了SHA-256作为NewHash。

请参见commit 0ed8d8d(2018年8月4日)由Jonathan Nieder(artagnon提交。
请参见commit 13f5e09(2018年7月25日)由Ævar Arnfjörð Bjarmason(avar提交。
(由Junio C Hamano -- gitster --合并于commit 34f2297,2018年8月20日)

文档 哈希函数转换:选择SHA-256作为NewHash

从安全角度来看,SHA-256、BLAKE2、SHA3-256、K12等算法被认为具有类似的安全性能。从安全角度来看,它们都是不错的选择。
SHA-256有许多优点: - 它已经存在一段时间了,被广泛使用,并且被几乎所有的加密库支持(OpenSSL、mbedTLS、CryptoNG、SecureTransport等)。 - 当与SHA1DC进行比较时,大多数矢量化的SHA-256实现即使没有加速也更快。 - 如果我们使用OpenPGP(或者甚至是CMS)进行签名,我们将使用SHA-2,因此在安全性依赖于两种单独的算法时,任何一种算法都可能破坏安全性,而我们可以只依赖一种算法。
所以就使用SHA-256吧。更新哈希函数转换设计文档以反映这一点。
在这个补丁之后,字符串“NewHash”的其余实例已经不存在,除了2008年在t/t9700/test.pl中作为一个变量名称的无关用途。

您可以在 Git 2.20 (2018年第四季度) 中看到这种转换到 SHA 256 的进展:

查看 commit 0d7c419, commit dda6346, commit eccb5a5, commit 93eb00f, commit d8a3a69, commit fbd0e37, commit f690b6b, commit 49d1660, commit 268babd, commit fa13080, commit 7b5e614, commit 58ce21b, commit 2f0c9e9, commit 825544a (2018年10月15日) 由 brian m. carlson (bk2204) 提交。
查看 commit 6afedba (2018年10月15日) 由 SZEDER Gábor (szeder) 提交。
(在 commit d829d49 中由 Junio C Hamano -- gitster -- 合并,于2018年10月30日)

替换硬编码常量

将几个基于40的常量替换为对GIT_MAX_HEXSZthe_hash_algo的引用,视情况而定。
将所有使用GIT_SHA1_HEXSZ的地方转换为使用the_hash_algo,以便适用于任何给定的哈希长度。
不要再使用硬编码常量来表示十六进制对象ID的大小,改为使用parse_oid_hex解析后指向对象ID之后的计算指针。 GIT_SHA1_HEXSZ在Git 2.22(2019年第二季度)进一步删除/替换,详见commit d4e568b

随着Git 2.21(2019年第一季度)的推出,这种过渡将继续,它添加了sha-256哈希并通过代码插入它,以允许使用“NewHash”构建Git。

请参见 commit 4b4e291commit 27dc04ccommit 13eeedbcommit c166599commit 37649b7commit a2ce0a7commit 50c817ecommit 9a3a0ffcommit 0dab712commit 47edb64(2018年11月14日)和commit 2f90b9dcommit 1ccf07c(2018年10月22日)由brian m. carlson (bk2204)
(由Junio C Hamano -- gitster --commit 33e4ae9,2019年1月29日合并)

增加SHA-256支持的基本实现(2019年2月)

SHA-1已经不安全了,我们需要过渡到一个新的哈希函数。
有一段时间,我们将这个新函数称为NewHash
最近,我们决定选择SHA-256作为NewHash
在这个讨论主题中解释了选择SHA-256的原因,在哈希函数过渡文档的提交历史中也有介绍。

基于公共领域中的libtomcrypt添加了SHA-256的基本实现,进行了优化和重构以满足我们的编码标准。从SHA-1块实现中提取出更新和最终函数,因为我们知道这些函数在所有编译器上都能正确运行。这个实现比SHA-1更慢,但更高性能的实现将在未来的提交中引入。

将SHA-256连接到哈希算法列表中,并添加一个测试,验证算法是否正确工作。

请注意,使用此补丁仍然不能切换到在Git中使用SHA-256。
需要其他补丁来准备代码以处理更大的哈希算法,还需要进一步修复测试问题。

hash:使用OpenSSL添加SHA-256实现

We already have OpenSSL routines available for SHA-1, so add routines for SHA-256 as well.

On a Core i7-6600U, this SHA-256 implementation compares favorably to the SHA1DC SHA-1 implementation:

SHA-1: 157 MiB/s (64 byte chunks); 337 MiB/s (16 KiB chunks)
SHA-256: 165 MiB/s (64 byte chunks); 408 MiB/s (16 KiB chunks)

sha256: 添加一个使用libgcrypt的SHA-256实现

通常,使用汇编语言编写的加密例程比C语言更具性能优势,对于SHA-256也是如此。
此外,由于许可证原因,大多数Linux发行版不能分发链接到OpenSSL的Git。
大多数带有GnuPG的系统也将拥有libgcrypt,因为它是GnuPG的依赖项。
对于几个KiB及以上的消息,libgcrypt也比SHA1DC实现更快。
例如,在Core i7-6600U上,此实现以355 MiB/s的速度处理16 KiB块,而SHA1DC处理等效块的速度为337 MiB/s。
此外,libgcrypt的许可证为LGPL 2.1,与GPL兼容。添加使用libgcrypt的SHA-256实现。

Git 2.24(2019年第4季度)的升级工作正在进行中。

查看commit aaa95df, commit be8e172, commit 3f34d70, commit fc06be3, commit 69fa337, commit 3a4d7aa, commit e0cb7cd, commit 8d4d86b, commit f6ca67d, commit dd336a5, commit 894c0f6, commit 4439c7a, commit 95518fa, commit e84f357, commit fe9fec4, commit 976ff7e, commit 703d2d4, commit 9d958cc, commit 7962e04, commit fee4930 (2019年8月18日) 由brian m. carlson (bk2204)提交。
(由Junio C Hamano -- gitster --commit 676278f合并,2019年10月11日)

不要使用GIT_SHA1_HEXSZ和硬编码的常量,改用the_hash_algo


在 Git 2.26(2020年第一季度)中,测试脚本已准备好应对对象名称使用 SHA-256 的情况。

请查看以下提交记录:commit 277eb5acommit 44b6c05commit 7a868c5commit 1b8f39fcommit a8c17e3commit 8320722commit 74ad99bcommit ba1be1acommit cba472dcommit 82d5aebcommit 3c5e65ccommit 235d3cdcommit 1d86c8fcommit 525a7f1commit 7a1bcb2commit cb78f4fcommit 717c939commit 08a9dd8commit 215b60bcommit 194264c(截至2019年12月21日),这些提交记录由brian m. carlson (bk2204)提交。
(于2020年2月5日合并,合并者为Junio C Hamano -- gitster --commit f52ab33

例子:

t4204: 使哈希大小独立

Signed-off-by: brian m. carlson

使用 $OID_REGEX 而不是硬编码的正则表达式。

因此,不要使用:

grep "^[a-f0-9]\{40\} $(git rev-parse HEAD)$" output

测试正在使用

grep "^$OID_REGEX $(git rev-parse HEAD)$" output

OID_REGEX 来自 commit bdee9cd (2018年5月13日)由 brian m. carlson (bk2204) 提交。
(由 Junio C Hamano -- gitster -- 合并在 commit 9472b13 中,于2018年5月30日,Git v2.18.0-rc0)

t/test-lib:引入 OID_REGEX

签名作者:brian m. carlson

目前我们有一个变量$_x40,其中包含匹配完整的40个字符十六进制常量的正则表达式。

然而,使用NewHash后,我们将拥有比40个字符更长的对象ID。

在这种情况下,$_x40将是一个令人困惑的名称。

创建一个$OID_REGEX变量,它将始终反映与当前哈希长度无关的适当对象ID匹配的正则表达式。

还有,用于测试:

请看commit f303765commit edf0424commit 5db24dccommit d341e08commit 88ed241commit 48c10cccommit f7ae8e6commit e70649bcommit a30f93bcommit a79eec2commit 796d138commit 417e45ecommit dfa5f53commit f743e8fcommit 72f936bcommit 5df0f11commit 07877f3commit 6025e89commit 7b1a182commit 94db7e3commit db12505 (2020年2月7日) 由brian m. carlson (bk2204)提交。
(由Junio C Hamano -- gitster --commit 5af345a合并,2020年2月17日)

t5703: 使测试能够与SHA-256一起使用

签名:brian m. carlson

该测试使用了一个40个十六进制字符长度的对象ID,导致测试不仅不能通过,而且在使用SHA-256作为哈希时会挂起。

使用test_oid_inittest_oid将此值更改为固定的虚拟对象ID。

此外,确保我们使用字段切割而不是固定长度来提取适当长度的对象ID。


一些代码路径将存储库实例作为参数传递给工作存储库的函数,并将the_repository实例传递给其调用者,这已在 Git 2.26(2020年第一季度)中得到了(某种程度上的)清理。

查看commit b98d188, commit 2dcde20, commit 7ad5c44, commit c8123e7, commit 5ec9b8a, commit a651946, commit eb999b3 (2020年1月30日) 由Matheus Tavares (matheustavares)提交。
(由Junio C Hamano -- gitster --合并于commit 78e67cd,2020年2月14日)

sha1-file: 允许 check_object_signature() 处理任何仓库

签名:Matheus Tavares

check_object_signature() 的某些调用者可以处理任意存储库,但存储库并未传递给该函数。相反,内部始终使用 the_repository
为了修复可能的不一致性,允许该函数接收一个 struct repository 并使这些调用者传递正在处理的存储库。

基于:

sha1-file: 将 git_hash_algo 传递给 hash_object_file()

签名:Matheus Tavares

引入一个git_hash_algo参数,使hash_object_file()能够在任意仓库上工作。更改具有struct repository指针的调用者的调用方式,以传递来自所述repo的git_hash_algo。
对于所有其他调用者,请传递内部已经在hash_object_file()中使用的the_hash_algo。
此功能将在以下补丁中使用,使check_object_signature()能够在任意仓库上工作(这将用于修复object.c:parse_object()中的不一致性)。

Git 2.38 (2022年第三季度)新增了对libnettle的支持,因为已添加了SHA256实现。

请参见提交e555735(2022年7月10日),由brian m. carlson (bk2204)进行。
(由Junio C Hamano -- gitster --合并于提交4af2138,2022年7月18日)

sha256:增加对Nettle的支持

签名:
brian m. carlson

对于SHA-256,我们目前支持OpenSSL和libgcrypt,因为这两个库包含了可以利用本地处理器指令的优化实现。然而:
- OpenSSL不适合与Linux发行版进行链接,因为其许可证与GPLv2不兼容; - libgcrypt由于一些安全相关的实现问题而被密码学家们较少使用,尽管这并不影响我们对哈希算法的使用,但它的声誉受到了影响。
让我们再添加一个与GPLv2兼容的选项Nettle。这是一个通常比libgcrypt更好的选择,因为在许多发行版中,GnuTLS(使用Nettle)用于HTTPS,因此从实际角度来看,在大多数系统上都会可用。因此,应该优先考虑它,而不是libgcrypt和我们内置的实现。
Nettle还最近增加了对Intel的SHA-NI指令的支持,这与其他实现相比非常有利,并且还提供了汇编实现,以便在没有SHA-NI时使用。
git.git上运行git gc(man),使用Nettle比我们的块SHA-256实现可以获得12%的性能提升,这是由于通用汇编改进所致。使用SHA-NI,2 GiB文件的原始SHA-256性能从块SHA-256的7.296秒提高到Nettle的1.523秒。

2
此外,不要忘记 Git v2.13.0 及更高版本默认采用了加固的 SHA-1 实现,不会受到 SHAttered 攻击的影响。请参阅 https://dev59.com/t2kv5IYBdhLWcg3wqimS#43355918。 - VonC
1
1:谷歌于2017年2月推出了碰撞检测网站https://shattered.io/(预估成本11万美元) 2:南洋理工大学于2019年1月发布了碰撞检测网站https://sha-mbles.github.io/ (预估成本在11k-45k美元之间)是时候让Git告别SHA1了。 - bristweb
@bristweb:“Git已经是时候超越SHA1了”:我同意,而且随着Git 2.25(今天发布),这一步已经迈出。git rev-parse现在能够打印将要使用的哈希值:https://dev59.com/p2ox5IYBdhLWcg3wl1P4#58862319。空树也有一个新的SHA2 id:https://dev59.com/UGkw5IYBdhLWcg3wm74h#9766506。 - VonC

54

更新: 上述问题和答案来自2015年。此后,谷歌宣布了第一个SHA-1碰撞:https://security.googleblog.com/2017/02/announcing-first-sha1-collision.html


显然,我只能从外部推测Git为什么继续使用SHA-1,但以下可能是原因之一:

  1. Git是Linus Torvald的创造物,他似乎目前不想用其他哈希算法替换SHA-1。
  2. 他声称成功的基于SHA-1碰撞的攻击比实现碰撞本身要难得多,并且考虑到SHA-1比应该弱,但并未完全破解,至少在今天,这使得成功的攻击远离可行。此外,他指出,“成功”的攻击如果碰撞对象晚于现有对象,则几乎不会产生任何效果,因为后者将被假定与有效对象相同并被忽略(尽管其他人已经指出反过来可能会发生)。
  3. 更改软件是耗时且容易出错的,特别是当已有基础架构和基于现有协议的数据需要迁移时。即使是那些产生的软件和硬件产品,其中加密安全性是系统的唯一点的人们在从SHA-1和其他弱算法中迁移的过程中仍然存在问题。想象一下到处都是硬编码的 unsigned char[20]缓冲区, 后期改动要比开始加密敏捷性更容易。
  4. SHA-1的性能比各种SHA-2哈希好(可能现在的优势不是那么明显,但10年前可能是一个瓶颈),而SHA-2的存储大小较大。

一些链接:

我个人的看法是,虽然实际攻击可能需要一些时间,即使攻击发生时人们可能会先采取其他手段来缓解而不是更改哈希算法本身,但如果您关心安全性,您应该在选择算法时谨慎行事,并不断提高安全强度,因为攻击者的能力也只朝着一种方向发展,因此以Git作为榜样是不明智的,特别是因为它使用SHA-1的目的并不是声称具有加密安全性。


7
你可能会遇到一些恶意的人,但他们不会成功。尚未有人能够破解SHA-1,但从Git的角度来看,SHA-1甚至不是一个安全功能,它只是一个一致性检查。-林纳斯·托瓦兹 - Shakti
11
Git的哈希需要保证安全,以确保人们对代码放置的安全签名可以验证任何内容。这些签名会签署这些哈希值的巨大树形结构。如果树形结构中的某个分支发生碰撞,则可能会在签名通过的同时插入恶意代码。Git现在被广泛使用,需要升级哈希算法。 - fuzzyTew
在“破碎”事件的光下需要考虑两件事:
  1. SHA-1的使用。
    • SHA-1被用作一种较为高级的校验和,以检查意外损坏。
    • SHA-1被用作生成函数,以提供一个(有点小)方便的十六进制数字来指定其内容可寻址存储中的对象(即:类似于文件名生成器)。
    • 它是签名提交负责安全性(即:公钥密码学签名。而不是SHA-1)。
- DrYak
  1. 可行性
    • 经过大量的GPU时间,谷歌成功生成了一对块系列。
    • 它们都哈希到相同的SHA-1总和(这就是碰撞)。
    • 它们完全是垃圾(这将很难证明为什么您的提交中间有一个巨大的二进制垃圾块)。
    • Shattered演示依赖于一种方法,可以根据存在哪些随机二进制垃圾来呈现不同的行为。这在PDF中是可能的(它有一个隐藏的嵌入式脚本语言)。在纯源代码上会更加困难(考虑Underhanded C Contest)。
- DrYak
1
@DrYak 对于第2点:假设您正在跟踪具有评论字段的Photoshop文档,或具有元标记的其他媒体文件。这是游戏开发中的典型案例。但通常不会在每次更改时检查它们:如果提交消息为“优化大小”,那么为什么要检查元标记呢? - Arne Babenhauserheide
1
"目前看来,Linus不想用其他哈希算法替换SHA-1。" - 自2005年末以来,该项目的领导者一直是Junio Hamano,而不是Linus。 - 1615903

5
这是一篇关于迫切需要迁移离开SHA1的讨论,虽然主要针对Mercurial,但同样适用于Git:https://www.mercurial-scm.org/wiki/mpm/SHA1 简而言之:如果你今天不非常勤奋,那么你比sha1更容易受到攻击。但尽管如此,Mercurial在10多年前就开始为迁移离开sha1做准备。

多年来,人们一直在修改Mercurial的数据结构和协议,以适应SHA1的后继者。在Mercurial 0.9中,随着RevlogNG的引入,我们为较大的哈希值分配了存储空间。最近引入的bundle2格式支持在网络上交换不同类型的哈希值。唯一剩下的问题是选择替代函数和选择向后兼容策略。

如果Git在Mercurial之前不迁移离开sha1,您可以通过使用hg-git保留本地Mercurial镜像来添加另一层安全性。

4
现在有一个过渡计划,要使用比SHA-1更强的哈希算法,因此未来将采用更现代的哈希算法。从当前的过渡计划中可以看出,一些考虑中的哈希算法包括SHA-256、SHA-512/256、SHA-256x16、K12和BLAKE2bp-256。请参考过渡计划当前的过渡计划了解更多信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接