Git 正在转向新的哈希算法 SHA-256,但为什么 git 社区选择了 SHA?256?

Tri*_*kar 13 git hash cryptography

我刚刚从这篇HN-post 中了解到 git 正在转向新的散列算法(从SHA-1SHA-256

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

Von*_*onC 26

我在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”,这样默认情况下实际上没有人看到新位
所以使用“的perl脚本等[0-9a-f]{40} ”作为散列模式只会默默地继续工作。

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

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

(SHA1DC,为“?检测()碰撞”,是在2017年年初讨论,碰撞后攻shattered.io例如:看到提交28dc98e,Git的v2.13.0-RC0三月2017年,由杰夫·金,以及“散列碰撞混蛋")


查看更多 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提交ddddf8d提交42d4e1d提交e02a714提交efa7ae3提交3c9331a提交8b8f718提交cfe3917提交bf154a8提交8dca7f3提交6946e52提交8bd5a29提交1f5f8f3提交192b517提交9412759提交61e2a70提交dadacf1提交 768e30e提交 2078991(2020 年 2 月 22 日)由Brian m。卡尔森 ( bk2204)
(由Junio C gitsterHamano合并-- --提交 f8cb64e 中,2020 年 3 月 27 日)

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

签字人:brian m. 卡尔森

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

添加一对选项, --rewrite-submodules-fromand --rewrite-submodules-to,它们在处理子模块时分别获取由fast-exportand生成的标记列表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>
Run Code Online (Sandbox Code Playgroud)

<name>from指定的子模块的对象 ID 重写为from 中使用的值<file>到 to 中使用的值<file>
from标志应已被创建git fast-export,并to通过应该已经创建的标记git fast-import导入相同的子模块时。

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

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

和:

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

签字人:brian m. 卡尔森

过渡计划预计我们将允许在一次提交中使用多种算法进行签名
为了做到这一点,我们需要为每个算法使用不同的标头,这样就可以清楚地知道要计算签名的数据。

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


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

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

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

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

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

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

  • 我们添加了一个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()可能会修改我们的顶级集指针,因此我们必须等到它返回后才能将其值赋给string_list.

  • @JurajMartinka我已经编辑了答案来解决这个重要的方面。 (2认同)