我刚刚从this HackerNews post那里了解到,Git正在迁移到一个新的哈希算法,从SHA-1
到SHA-256
。
是什么让SHA-256
最适合Git的用例?是否有任何/许多强大的技术原因,或者SHA-256
的流行是一个强大的因素?
看看Wikipedia's hash function comparison,我发现有很多现代和旧的替代品,其中一些比SHA-256
,like SHA-512
性能更好(或类似)。
我刚刚从this HackerNews post那里了解到,Git正在迁移到一个新的哈希算法,从SHA-1
到SHA-256
。
是什么让SHA-256
最适合Git的用例?是否有任何/许多强大的技术原因,或者SHA-256
的流行是一个强大的因素?
看看Wikipedia's hash function comparison,我发现有很多现代和旧的替代品,其中一些比SHA-256
,like SHA-512
性能更好(或类似)。
1条答案
按热度按时间bybem2ql1#
我已经在Aug. 2018中的“Why doesn't Git use more modern SHA?“中提出了这一步
Brian M. Carlson的discussed here:
我已经实现并测试了以下算法,它们都是256位的(按字母顺序排列):
我也拒绝了其他一些候选人。
我找不到SHA 256 ×16的任何参考或实现,所以我没有实现它。
我没有考虑SHAKE 256,因为它几乎在所有特性(包括性能)上都与SHA 3 -256几乎相同。
SHA-256和SHA-512/256
这些是32位和64位SHA-2算法,大小为256位。
我注意到以下好处:
摘要
实现可用性最高的算法是SHA-256、SHA 3 -256、BLAKE 2b和SHAKE 128。
在命令行可用性方面,BLAKE 2b,SHA-256,SHA-512/256和SHA 3 -256应该在不久的将来在一个相当小的Debian,Ubuntu或Fedora安装上可用。
就安全性而言,最保守的选择似乎是SHA-256、SHA-512/256和SHA 3 -256。
性能冠军是BLAKE 2b未加速和SHA-256加速。
suggested conclusion was based on:
人气
在其他条件相同的情况下,我们应该偏向于最广泛使用的东西,并推荐用于新项目。
硬件加速
唯一广泛部署的硬件加速是SHA-1和SHA-256 from the SHA-2 family,但值得注意的是,新的SHA-3系列(2015年发布)没有。
年龄
与“流行度”类似,似乎更好地将事情偏向于已经存在一段时间的散列,即选择SHA-3还为时过早。
散列转换计划一旦实施,也可以更容易地在未来切换到其他东西,所以我们不应该急于选择一些新的散列,因为我们需要永远保留它,我们总是可以在另一个10-15年内进行另一次转换。
结果:commit 0ed8d8d,Git v2.19.0-rc 0,2018年8月4日。
SHA-256有很多优点:
SHA-256是什么意思
这个想法仍然存在:任何SHA1的概念都将从Git代码库中删除,并被通用的“哈希”变量所取代。
明天,该哈希将是SHA 2,但代码将在未来支持其他哈希。
作为Linus Torvalds delicately puts it(强调我的):
老实说,在可观测的宇宙中,粒子的数量大约是2**256。这是一个非常非常大的数字。
不要让代码库比它需要的更复杂。
做一个明智的技术决定,说“256位是一个 * 很多 *”。
**工程学和理论的区别在于工程学需要权衡取舍。
好的软件是精心设计的,而不是理论化的。
另外,我建议git默认为“
abbrev-commit=40
“,这样就没有人会真正 * 看到 * 默认的新位。因此,使用“
[0-9a-f]{40}
“作为哈希模式的perl脚本等将只是默默地继续工作。因为向后兼容性很重要()
()2**160仍然是一个很大的数字,并没有真正成为一个实际问题,SHA1 DC可能是未来十年或更长时间的好散列。
(SHA1 DC,代表“检测(?)碰撞”,在2017年初讨论过,在碰撞攻击shattered.io示例之后:参见commit 28dc98e,Git v2.13.0-rc 0,2017年3月,来自Jeff King和“Hash collision in git“)
查看更多
Documentation/technical/hash-function-transition.txt
向SHA-256的转换可以一次完成一个本地存储库。
a.不要求任何其他方采取行动。
B. 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>
参见commit 1bdca81、commit d9db599、commit 11d8ef3、commit abe0cc5、commit ddddf8d、commit 42d4e1d、commit e02a714、commit efa7ae3、commit 3c9331a、commit 8b8f718、commit cfe3917、commit bf154a8、commit 8dca7f3、commit 6946e52、commit 8bd5a29、commit 1f5f8f3、commit 192b517,commit 9412759,commit 61e2a70,commit dadacf1,commit 768e30e,commit 2078991(2020年2月22日)by brian m. carlson (
bk2204
).(由Junio C Hamano --
gitster
--合并至commit f8cb64e,2020年3月27日)fast-import
:增加重写子模块的选项签字人:Brian M. Carlson
当使用子模块将仓库从一种哈希算法转换为另一种哈希算法时,需要将子模块从旧算法重写为新算法,因为只有子模块的引用而不是其内容会写入快速导出流。
如果不重写子模块,当遇到另一个算法中的子模块时,快速导入会失败并出现“
Invalid dataref
“错误。添加一对选项
--rewrite-submodules-from
和--rewrite-submodules-to
,它们在处理子模块时分别接受fast-export
和fast-import
生成的标记列表。使用这些标记将子模块提交从旧算法Map到新算法。
我们将标记读取到两个对应的struct
mark_set
对象中,然后使用哈希表执行从旧到新的Map。这使我们可以重用在其他地方使用的相同标记解析代码,并允许我们根据标记的ID有效地读取和匹配标记,因为标记文件不需要排序。请注意,因为我们使用
khash
表作为对象ID,并且该表复制了结构object_id
的值,而不是引用它们,所以有必要将用于插入和查找表的结构object_id
值置零。否则,我们最终会得到不匹配的SHA-1值,因为任何堆栈垃圾可能留在未使用的区域。git fast-import
文档现在包括:子模块重写
字符串
将
<name>
指定的子模块的对象ID从from<file>
中使用的值重写为to<file>
中使用的值。from
标记应该由git fast-export
创建,to
标记应该由git fast-import
在导入同一子模块时创建。<name>
可以是任何不包含冒号字符的任意字符串,但在指定相应的标记时,两个选项必须使用相同的值。可以为多个子模块指定不同的值。不将这些选项成对使用是错误的。
这些选项主要用于将存储库从一种哈希算法转换为另一种哈希算法时;如果没有它们,快速导入在遇到子模块时将失败,因为它无法将对象ID写入新的哈希算法。
并且:
commit
:使用SHA-256的预期签名头签字人:Brian M. Carlson
过渡计划预计我们将允许在一次提交中使用多种算法进行签名。
为了做到这一点,我们需要为每个算法使用不同的头部,以便在哪个数据上计算签名是显而易见的。
转换计划指定我们应该使用“
gpgsig-sha256
“,因此将提交代码连接起来,这样它就可以编写和解析当前算法,并且可以在创建新提交时删除任何算法的头部。添加测试以确保我们使用正确的头部进行写入,并且
git fsck
不会拒绝这些提交。注意:最后的快速导入进化有一个讨厌的副作用:“
git fast-import
“(man)在使用许多标记时浪费了大量内存。Git 2.30(2020年第一季度)将修复此问题
参见commit 3f018ec(2020年10月15日)Jeff King (
peff
)。(由Junio C Hamano --
gitster
--合并于commit cd47bbe,2020年11月2日)fast-import
:修复标记存储过度分配问题作者:Sergey Brester
签名人:Jeff King
快速导入将其标记存储在由
mark_set
结构体组成的类似trie的结构中。(Trie: digital tree)
每个结构体都有一个固定的大小(1024)。如果我们的id号太大,无法容纳在结构体中,那么我们分配一个新的结构体,它将id号移动10位。我们的原始结构体成为这个新层的子节点,新结构体成为trie的顶层。
这个方案被ddddf8d7e2破坏了。(“
fast-import
:permit阅读multiple marks files”,2020-02-22,Git v2.27.0-rc 0--merge listed in batch #2).在此之前,我们有一个顶级的“marks”指针,下推的工作方式是将新的顶级结构体赋给“marks”。但在那次提交之后,insert_mark()
使用指向mark_set,
的指针,而不是使用全局“marks”。它在下推过程中继续分配给全局“marks”变量,这是错误的,原因有二:option_rewrite_submodules()
中添加了一个调用,它使用了一个单独的标记集;在这里向下推“marks”是完全错误的。我们会破坏“marks”集,并且我们无法正确存储任何id超过1024的子模块Map。read_mark_file()
中,我们将指向mark_set
的指针作为参数。因此,即使insert_mark()
正在更新全局“marks”,我们在read_mark_file()
中的本地指针也没有更新。因此,我们需要在需要时添加一个新级别,但是下一次对insert_mark()
的调用就看不到它了!然后它会分配一个新的层,这个层也不会被看到,等等。查找丢失的层显然是不起作用的,但是在我们进入任何查找阶段之前,我们通常会耗尽内存并死亡。我们的测试没有注意到这两种情况,因为它们没有足够的标记来触发下推行为。t9304中的新测试涵盖了这两种情况(没有这个补丁就会失败)。
我们可以通过让
insert_mark()
获取集合顶层的指针来解决这个问题。然后我们的下推可以以调用者实际看到的方式分配给它。注意option_rewrite_submodules()
中的微妙重新排序。我们对read_mark_file()
的调用可能会修改我们的顶层集合指针,所以我们必须等到它返回后才能将其值分配给string_list
。