首页 > 解决方案 > Git 正在转向新的哈希算法 SHA-256 但为什么 git 社区选择了 SHA-256

问题描述

我刚刚从这个HN-post了解到 git 正在转向新的哈希算法( from SHA-1to SHA-256

我想知道什么SHA-256最适合 git 的用例。是否有任何/许多强有力的技术原因,或者SHA-256受欢迎程度是否可能是一个重要因素?(我在猜测)查看https://en.wikipedia.org/wiki/Comparison_of_cryptographic_hash_functions页面,我发现存在许多现代和较旧的替代方案。其中一些比SHA-256(例如https://crypto.stackexchange.com/q/26336)性能更高(如果不是更多,几乎相同)和更强

标签: githashcryptography

解决方案


我在2018 年 8 月的“为什么 Git 不使用更现代的 SHA? ”中介绍了这一举措

Brian M. Carlson在此讨论了原因:

我已经实现并测试了以下算法,所有这些算法都是 256 位的(按字母顺序排列):

  • BLAKE2b (libb2)
  • BLAKE2bp (libb2)
  • KangarooTwelve(从 Keccak 代码包导入)
  • SHA-256 (OpenSSL)
  • SHA-512/256 (OpenSSL)
  • SHA3​​-256 (OpenSSL)
  • SHAKE128 (OpenSSL)

我还拒绝了其他一些候选人。
我找不到任何 SHA256×16 的参考或实现,所以我没有实现它。
我没有考虑 SHAKE256,因为它几乎在所有特性(包括性能)上都与 SHA3-256 相同。

SHA-256 和 SHA-512/256

这些是 256 位大小的 32 位和 64 位 SHA-2 算法。

我注意到以下好处:

  • 这两种算法都是众所周知的并且经过大量分析。
  • 两种算法都提供 256 位原像电阻。

概括

实现可用性最高的算法是 SHA-256、SHA3-256、BLAKE2b 和 SHAKE128。

就命令行可用性而言,BLAKE2b、SHA-256、SHA-512/256 和 SHA3-256 应该在不久的将来在相当小的 Debian、Ubuntu 或 Fedora 安装中可用。

就安全性而言,最保守的选择似乎是 SHA-256、SHA-512/256 和 SHA3-256。

性能优胜者是未加速的 BLAKE2b 和加速的 SHA-256。

建议的结论基于

人气

在其他条件相同的情况下,我们应该偏向于最广泛使用并推荐给新项目的任何东西。

硬件加速

唯一广泛部署的硬件加速是针对SHA-2 系列的 SHA-1 和 SHA-256 ,但值得注意的是较新的 SHA-3 系列(2015 年发布)中没有。

年龄

与“流行度”类似,将事物偏向于已经存在一段时间的哈希似乎更好,即选择 SHA-3 还为时过早。

哈希转换计划一旦实施,将来也更容易切换到其他东西,所以我们不应该急于选择一些更新的哈希,因为我们需要永远保留它,我们总是可以做另一个再过10-15年的过渡期。

结果:提交 0ed8d8d,Git v2.19.0-rc0,2018 年 8 月 4 日。

SHA-256 有许多优点:

  • 它已经存在了一段时间,被广泛使用,几乎每个加密库(OpenSSL、mbedTLS、CryptoNG、SecureTransport 等)都支持它。

  • 与 SHA1DC 进行比较时,大多数矢量化 SHA-256 实现确实更快,即使没有加速也是如此。

  • 如果我们使用 OpenPGP(或者甚至,我想是 CMS)进行签名,我们将使用 SHA-2,因此当我们的安全性依赖于两个单独的算法时,它们中的任何一个都是没有意义的当我们只能依靠一个人时,一个人可能会破坏安全性。

所以 SHA-256 就是这样。

这个想法仍然存在:SHA1 的任何概念都将从 Git 代码库中删除,并由通用的“散列”变量代替。
明天,该散列将是 SHA2,但代码将来会支持其他散列。

正如Linus Torvalds 巧妙地指出的那样(强调我的):

老实说,可观测宇宙中的粒子数量在 2**256 的数量级上。这是一个非常非常大的数字。

不要让代码库变得比它需要的更复杂。
做出明智的技术决定,并说“256 位很多”。

工程学和理论之间的区别在于工程学会做出权衡。
好的软件是精心设计的,而不是理论化
的。

另外,我建议 git 默认为“ abbrev-commit=40”,这样默认情况下没有人真正看到新位
因此,使用“ [0-9a-f]{40}”作为哈希模式的 perl 脚本等只会默默地继续工作。

因为向后兼容性很重要 (*)

(*) 而且 2**160 仍然是一个很大的数字,并没有真正成为一个实际问题,而 SHA1DC 很可能是未来十年或更长时间的一个很好的哈希值。

(SHA1DC,用于“Detecting(?) Collision”,在碰撞攻击shattered.io实例之后于 2017 年初进行了讨论:参见提交 28dc98e,Git v2.13.0-rc0,2017 年 3 月,来自Jeff King和“ Hash collision in混帐")


查看更多内容Documentation/technical/hash-function-transition.txt

一次可以在一个本地存储库中转换到 SHA-256。

一个。不需要任何其他方采取任何行动。
湾。SHA-256 存储库可以与 SHA-1 Git 服务器通信(推送/获取)。
C。用户可以交替使用对象的 SHA-1 和 SHA-256 标识符(请参阅下面的“命令行上的对象名称”)。
d。新的签名对象使用比 SHA-1 更强大的散列函数来保证其安全性。


Git 2.27(2020 年第二季度)和它的git fast-import --rewrite-submodules-from/to=<name>:<file>

参见提交1BDCA81提交D9DB599提交11D8EF3提交ABE0CC5 ,提交DDDDDF8D,提交42D4E1D ,COM​​MIT E02A714, COMMIT EFA7AE3,COMMIT 3C93331A,提交3C9331A,提交8B8F718,提交CFE 3917 ,提交3917 ,commit Bff5517,154.a8d.1a, 8dd.1a,8dd.a,8dd.1a,8dd.a,8dd.a,a,8dd.1a,8dd.1a,8dd.1a,8dd.a,y.a ,y.ay.1ay.ay.ay.a.an。a 192b517提交 9412759提交 61e2a70提交 dadacf1提交 768e30e提交 2078991(2020 年 2 月 22 日),作者:brian m。卡尔森 ( bk2204) .
(由Junio C Hamano 合并 -- gitster--提交 f8cb64e中,2020 年 3 月 27 日)

fast-import: 添加重写子模块的选项

签字人:brian m. 卡尔森

当使用子模块将存储库从一种哈希算法转换为另一种时,有必要将子模块从旧算法重写为新算法,因为只有对子模块的引用,而不是它们的内容,才会写入快速导出流。在不重写子模块的情况下,当遇到另一个算法中
的子模块时,快速导入会失败并出现“ ”错误。Invalid dataref

添加一对选项--rewrite-submodules-from和,它们在处理子模块时分别--rewrite-submodules-to采用由fast-export和产生的标记列表。 使用这些标记将子模块提交从旧算法映射到新算法。fast-import

我们将标记读入两个对应的结构mark_set对象,然后使用哈希表执行从旧到新的映射。这让我们可以重用在其他地方使用的相同的标记解析代码,并允许我们根据它们的 ID 有效地读取和匹配标记,因为不需要对标记文件进行排序。

请注意,因为我们使用一个khash表作为对象 ID,并且该表复制 struct 的值object_id而不是引用它们,所以有必要将object_id我们用于在表中插入和查找的 struct 值归零。否则,我们最终会得到不匹配的 SHA-1 值,因为任何堆栈垃圾都可能留在未使用的区域中。

git fast-import文档现在包括:

子模块重写

--rewrite-submodules-from=<name>:<file>
--rewrite-submodules-to=<name>:<file>

<name>将from 中使用的值重写为<file>to 中使用的子模块的对象 ID <file>。标记应该是由 创建的
,并且标记应该是在导入同一个子模块时创建的。fromgit fast-exporttogit fast-import

<name>可以是任何不包含冒号字符的任意字符串,但在指定相应标记时,两个选项必须使用相同的值。
可以为多个子模块指定不同的值。不成对使用这些选项是错误的。

这些选项主要在将存储库从一种哈希算法转换为另一种时有用;没有它们,如果遇到子模块,快速导入将失败,因为它无法将对象 ID 写入新的哈希算法。

和:

commit: 使用 SHA-256 的预期签名标头

签字人:brian m. 卡尔森

过渡计划预计我们将允许在一次提交中使用多种算法的签名
为了做到这一点,我们需要为每个算法使用不同的标头,以便计算签名的数据是显而易见的。

转换计划指定我们应该使用“ gpgsig-sha256”,因此连接提交代码以便它可以编写和解析当前算法,并且可以在创建新提交时删除任何算法的标头。
添加测试以确保我们使用正确的标头进行编写并且git fsck不会拒绝这些提交。


注意:最后的快速导入进化有一个讨厌的副作用:“ git fast-importman在使用许多标记时浪费了大量内存。
这应该通过 Git 2.30(2020 年第一季度)修复

请参阅Jeff King ( ) 的提交 3f018ec(2020 年 10 月 15 日(由Junio C Hamano 合并 -- --cd47bbe 提交中,2020 年 11 月 2 日)peff
gitster

fast-import:修复标记存储的过度分配

报告人:Sergey Brester
签字人:Jeff King

快速导入将其标记存储在由结构组成的类似 trie 的mark_set结构中。
Trie:数字树
每个结构都有固定的大小(1024)。如果我们的 id 号太大而无法放入结构中,那么我们分配一个新的结构,它将 id 号移动 10 位。我们原来的 struct 成为这个新层的子节点,新的 struct 成为 trie 的顶层。

该方案被ddddf8d7e2 破坏(“ fast-import:允许读取多个标记文件”,2020-02-22,Git v2.27.0-rc0 --合并在批次 #2中列出)。在此之前,我们有一个顶级“marks”指针,并且通过将新的顶级结构分配给“marks”来实现下推。但是在那次提交之后,insert_mark()需要一个指向mark_set,而不是使用全局“标记”的指针。它在下推期间继续分配给全局“标记”变量,这是错误的,原因有两个:

  • 我们添加了一个option_rewrite_submodules()使用单独标记集的调用;在这里按下“标记”是完全错误的。我们会破坏“标记”集,并且我们无法正确存储任何 id 超过 1024 的子模块映射。
  • 其他呼叫者通过了“标记”,但下推仍然是错误的。在read_mark_file()中,我们将指向 的指针mark_set作为参数。因此,即使insert_mark()正在更新全局“标记”,我们所拥有的本地指针也read_mark_file()没有更新。结果,我们会在需要时添加一个新关卡,但是下一次调用insert_mark()就看不到它了!然后它会分配一个新的层,它也不会被看到,等等。查找丢失的层显然是行不通的,但在我们到达任何查找阶段之前,我们通常会耗尽内存并死掉。

我们的测试没有注意到这两种情况,因为它们没有足够的标记来触发下推行为。t9304 中的新测试涵盖了这两种情况(没有这个补丁就会失败)。

insert_mark()我们可以通过获取集合顶层的指针来解决这个问题。然后我们的下推可以以调用者实际看到的方式分配给它。请注意option_rewrite_submodules(). 我们的调用read_mark_file()可能会修改我们的顶级 set 指针,因此我们必须等到它返回后才能将其值分配给string_list.


推荐阅读