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节。