深入浅出密码学
上QQ阅读APP看书,第一时间看更新

2.1 什么是哈希函数

现在,我们可以看到一个网页(见图2.1),而下载(DOWNLOAD)按钮占据该网页的大部分空间。通过单击下载(DOWNLOAD)按钮,我们会跳转到一个包含待下载文件的网站。在这个按钮的下面有一长串难以理解的字符串:

f63e68ac0bf052ae923c03f5b12aedc6cca49874c1c9b0ccf3f39b662d1f487b

后面还有一串看起来像某种首字母缩略词的字符(sha256sum:)。这个字符串看起来是不是有点眼熟呢?在生活中,我们可能也下载过附带类似字符串格式的文件。

我们可以利用这个长字符串按照如下步骤来检测文件的完整性:

(1)点击按钮来下载文件;

(2)采用SHA-256算法来计算下载文件的哈希值;

(3)将哈希函数的输出(摘要)与网页上显示的字符串进行比较。

这个字符串还可以帮助我们验证下载的文件是否正确。

图2.1 该网页链接到包含一个文件的外部网站。该网页提供了该文件的哈希值,因为哈希值可以保证外部网站无法随意修改文件内容而不被察觉。该文件的哈希或摘要确保了下载文件的完整性

注意:

哈希函数的输出通常被称为摘要(Digest)或哈希值(Hash)。本书将交替使用这两个词。其他书可能会称哈希函数为校验和(Checksum),但因为这个术语主要用于指代非密码学的哈希函数,所以本书没有使用这个名词。我们只需要牢记,不同的代码库或文件会使用不同的术语。

当我们想要尝试计算哈希值时,可以使用流行的OpenSSL库。该库提供了一个多用途的命令行接口(Command Line Interface,CLI),macOS之类的许多系统中自带该命令行工具。例如,打开终端并输入如下命令:

$ openssl dgst -sha256 downloaded_file
f63e68ac0bf052ae923c03f5b12aedc6cca49874c1c9b0ccf3f39b662d1f487b

通过上述命令,我们可以使用SHA-256哈希函数把输入(下载的文件)转化为一个唯一的标识符(命令返回的值)。执行这些额外操作的目的是,检验文件的完整性(Integrity)和真实性(Authenticity),保证下载的文件确实是我们想要的。

这些工作,要归功于哈希函数的安全性质——抗第二原像性。这个术语意味着从这个哈希函数的长输出f63e...中,我们无法推断出另一个文件也可以通过相同的哈希函数得到相同的输出f63e...。在实践中,这意味着该摘要与正在下载的文件密切相关,没有攻击者能够不知不觉地将原文件替换为不同的文件。

十六进制编码

顺便说一下,上面的字符串f63e...采用的是十六进制(Base16编码,使用从0到9的数字和从a到f的字母来表示任意数据)表示形式。我们本可以用包含0和1的二进制编码方法表示哈希函数的输出,但这样会占用更多空间。十六进制编码允许我们将8比特(1字节)数编码成2个字符。这种编码方式具有可读性强、占用的空间少的优点。我们还可以使用其他的编码方法将二进制数据编码成可读字符,但十六进制编码和Base64编码是使用最多的两种编码方式。在Base系列编码中,基越大,编码二进制数据需要的字符数就越多。当然,如果用的基过大,我们可能就无法用已有的可读字符编码二进制数据。

请注意,这个长字符串由网页的所有者控制,任何能够修改该网页的人都可修改该字符串。(如果不相信这一点,请花点儿时间思考一下原因。)因此,为了确保文件的完整性,我们需要信任包含摘要字符串的网页、网页的所有者以及获取网页页面的安全机制,而不必信任包含下载文件的网页。从这个意义上说,单独使用一个哈希函数并不能提供完整性。因为下载文件的完整性和真实性来自文件摘要以及摘要的可信机制(在本例中为HTTPS)。我们将在第9章讨论HTTPS,但现在我们先假设该协议允许我们与网站进行安全通信。

我们可以将哈希函数看作图2.2中的黑匣子。我们的黑匣子接收一个输入并产生一个输出。

图2.2 哈希函数可以接收任意长度的输入(文件、消息、视频等)并产生固定长度的输出(例如,SHA-256算法的输出为256比特)。对于同一个哈希函数,相同的输入会产生相同的摘要或哈希值

哈希函数的输入可以是任意大小,甚至可以是空值,而它的输出的长度总是固定的,且具有确定性:给定相同的输入,哈希函数总是产生相同的输出。在我们的示例中,SHA-256始终提供256比特(32字节)的输出,它的输出终被编码为64个十六进制字符。哈希函数的一个主要特性是无法求逆,也就是说无法从输出中找到输入。因此,我们说哈希函数是单向(One-way)的。

为了说明哈希函数在实践中的工作原理,我们将使用OpenSSL命令行工具中的SHA-256哈希函数计算不同输入的哈希值。在OpenSSL命令行工具中,SHA-256算法的输入和输出如下所示:

$ echo -n “hello” | openssl dgst -sha256
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 
$ echo -n “hello” | openssl dgst -sha256    ←--- 对同样的输入计算哈希值,会得到相同的输出
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
$ echo -n “hella” | openssl dgst -sha256    ←--- 对哈希函数的输入内容的细微修改会得到完全不同的哈希值
70de66401b1399d79b843521ee726dcec1e9a8cb5708ec1520f1f3bb4b1dd984
$ echo -n “this is a very very very very very very    ←--- 无论输入的消息有多长,哈希函数输出值的长度总是固定的
  ➥ very very very long sentence” | openssl dgst -sha256            
1166e94d8c45fd8b269ae9451c51547dddec4fc09a91f15a9e27b14afee30006

在2.2节中,我们将看到哈希函数的其他性质。