6.2 思考:SHA1哈希值到底是什么,是如何生成的
哈希(hash)[1]是一种数据摘要算法(或称散列算法),是信息安全领域中重要的理论基石。该算法将任意长度的输入经过散列运算转换为固定长度的输出。固定长度的输出可以称为对应输入内容的数字摘要或哈希值。例如SHA1摘要算法可以处理从零到两百多万TB[2]的输入数据,输出为固定的160比特的数字摘要。即使两个不同内容的输入数据量非常大、差异非常小,两者的哈希值也会显著不同。比较著名的摘要算法有:MD5和SHA1。Linux下sha1sum命令可以用于生成摘要。
$printf Git|sha1sum
5819778898df55e3a762f0c5728b457970d72cae-
可以看出字符串Git的SHA1哈希值由40个十六进制的数字组成。那么能不能找出另外一个字符串使其SHA1哈希值和上面的哈希值一样呢?下面来看看难度有多大。
每个十六进制的数字相当于一个4位的二进制数字,因此40位的SHA1哈希值的输出实际为160比特。拿双色球博彩打一个比喻,要想制造相同的SHA1哈希值就相当于要选出32个“红色球”,每个红球有1到32个(5位的二进制数字)选择,不但红球之间可以重复,而且选出红球的顺序必须一致。相比“双色球博彩”总共只须选出6颗红球(1到33)外加1个篮球(1到16),SHA1“中奖”的难度差不多相当于要连续购买七期[3][4]“双色球”并且每一期都必需中一等奖。当然由于算法上的问题,制造冲突(相同数字摘要)的几率没有那么小[5],但是已经足够小了,能够满足Git对不同的对象进行区分和标识了。即使有一天像发现了类似MD5摘要算法的冲突那样,发现了SHA1算法存在人为制造冲突的可能,那么Git可以使用更为安全的SHA-256或SHA-512的摘要算法。
可是Git中的各种对象:提交(commit)、文件内容(blob)、目录树(tree)等[6],其对应的SHA1哈希值是如何生成的呢?下面就来展示一下。
先看一看提交的SHA1哈希值生成方法。
(1)看看HEAD对应的提交的内容,使用git cat-file命令。
$git cat-file commit HEAD
tree f58da9a820e3fd9d84ab2ca2f1b467ac265038f9
parent a0c641e92b10d8bcca1ed1bf84ca80340fdefee6
author Jiang Xin<jiangxin@ossxp.com>1291022581+0800
committer Jiang Xin<jiangxin@ossxp.com>1291022581+0800
which version checked in?
(2)提交信息中总共包含234个字符。
$git cat-file commit HEAD|wc-c
234
(3)在提交信息的前面加上内容commit 234<null>(<null>为空字符),然后执行SHA1哈希算法。
$(printf "commit 234\000";git cat-file commit HEAD)|sha1sum
e695606fc5e31b2ff9038a48a3d363f4c21a3d86-
(4)上面命令得到的哈希值和用git rev-parse看到的是一样的。
$git rev-parse HEAD
e695606fc5e31b2ff9038a48a3d363f4c21a3d86
下面来看一看文件内容的SHA1哈希值生成方法。
(1)看看版本库中welcome.txt的内容,使用git cat-file命令。
$git cat-file blob HEAD:welcome.txt
Hello.
Nice to meet you.
(2)文件总共包含25字节的内容。
$git cat-file blob HEAD:welcome.txt|wc-c
25
(3)在文件内容的前面加上blob 25<null>的内容,然后执行SHA1哈希算法。
$(printf "blob 25\000";git cat-file blob HEAD:welcome.txt)|sha1sum
fd3c069c1de4f4bc9b15940f490aeb48852f3c42-
(4)上面的命令得到的哈希值和用git rev-parse看到的是一样的。
$git rev-parse HEAD:welcome.txt
fd3c069c1de4f4bc9b15940f490aeb48852f3c42
最后再来看看树的SHA1哈希值的形成方法。
(1)HEAD对应的树的内容共包含39个字节。
$git cat-file tree HEAD^{tree}|wc-c
39
(2)在树的内容的前面加上tree 39<null>的内容,然后执行SHA1哈希算法。
$(printf "tree 39\000";git cat-file tree HEAD^{tree})|sha1sum
f58da9a820e3fd9d84ab2ca2f1b467ac265038f9-
(3)上面的命令得到的哈希值和用git rev-parse看到的是一样的。
$git rev-parse HEAD^{tree}
f58da9a820e3fd9d84ab2ca2f1b467ac265038f9
后面在学习里程碑(Tag)的时候会看到,Tag对象(轻量级Tag除外)也是采用类似的方法在对象库中存储的。
[1]http://en.wikipedia.org/wiki/Cryptographic_hash_function#cite_ref-13
[2]264-1比特,相当于209万TB。1TB相当于1024GB。
[3]160比特分成32组,每组5个比特。
[4]160÷24≈6.6
[5]相当于连续购买两期双色球彩票且都中一等奖。
[6]还有tag对象,参见第3篇第17.2.2节。