单向散列函数既是一个单向函数,也是一个散列函数。它不仅要满足单向函数的要求,还要满足散列函数的要求。你还记得这两种函数的要求吗?其中,最要紧的就是:
- 逆向运算困难;
- 构造碰撞困难。
大部分的hashCode()方法的实现,都满足不了逆向运算困难的要求,所以它们是不能算作单向散列函数的。比如说,按照Java的hashCode()方法的实现,32位整数的哈希值是这个整数本身,所以逆向运算一点难度都没有,当然不能算作单向散列函数。
单向散列函数是一定要逆向运算困难的。
至于构造碰撞困难,我用现成的单向散列函数给你举一个例子,比如SHA-1算法,它是一个常见的适用于密码学的单向散列函数。
现在,你面前有两句话,分别是“Hello, world!”和“Hello, vorld!”,这两句话只有一位的差异(w: 119/01110111, v: 118/01110110),我把它们的SHA-1算法计算出来的散列值,列在了下面。
你可以对比两个散列值,感受一下一个位的输入数据差异,计算出的散列值能有多大的差异。
SHA-1("Hello, world!):
10010100 00111010 01110000 00101101 00000110 11110011 01000101 10011001 10101110 11100001 11111000 11011010 10001110 11111001 11110111 00101001 01100000 00110001 11010110 10011001
SHA-1("Hello, vorld!):
11001011 11111111 11111011 10010011 01010111 11000010 10001101 01011000 00100010 11000100 01010110 10000110 00101010 00110011 01010000 10111110 10000010 01111111 00100000 10101010
是不是差异还挺大的?这种现象,我们把它叫做雪崩效应。
雪崩效应(Avalanche Effect)是密码学算法一个常见的特点,指的是输入数据的微小变换,就会导致输出数据的巨大变化。严格雪崩效应是雪崩效应的一个形式化指标,我们也常用来衡量均匀分布。 严格雪崩效应指的是,如果输入数据的一位反转,输出数据的每一位都有50%的概率会发生变化。
一个适用于密码学的单向散列函数,就要具有雪崩效应的特点,也就是说,如果一个单向散列函数具有雪崩效应,那么对于给定的数据,构造出一个新的、具有相同散列值的数据是困难的。
在这一讲的一开始,我们说过,密码学的单向散列函数是用来解决数据完整性问题的。那么,单向散列函数是怎么解决数据完整性问题的呢?
怎么解决完整性问题?
想要解决完整性问题,我们就要知道完整性问题的背后逻辑是什么。
完整性意味着什么?完整性的核心是 数据未经授权,不得更改。对于“不得更改”这四个字,你最直观的感受是什么?是不是无论如何,数据都没有办法改动?这是一个很强的解读。一般情况下,也很难有满足的场景。
还有一种站在反面看的、曲线的解读,就是如果数据有变动,能够被检测出来,我们就不采纳被篡改的数据。使用单向散列函数,就可以通过检查数据是否有变动,来解决数据完整性问题。
我们刚才说了,在单向散列函数里,一段数据,无论它是少了一个字,多了一个字,或者修改了一个字,原始数据和修改后的数据的散列值都可能相差巨大。
而且,由于逆向运算困难,虽然存在具有相同散列值的两个或者多个数据,但是对于一个好的单向散列函数来说,刻意寻找这样的数据是困难的。如果困难程度足够大,我们就有足够信心认为,如果散列值没有变化,它对应的输入数据也没有变化。
所以,单向函数和散列函数的组合,单向散列函数,就可以帮助我们解决完整性问题。
假如我们收到了一段数据,我们就可以重新计算这段数据的散列值。如果我们还可以获得数据发送者计算的散列值,我们就可以对比新计算的散列值和接收到的散列值。如果两个散列值是相同的,我们就可以认为这段数据是完整的;否则,这段数据就是被篡改过的。
输入:
1、数据D
2、原始数据的散列值H
3、计算散列值使用的散列函数
输出:
数据D是不是完整的?
运算:
1、使用散列函数计算数据D的散列值H';
2、对比数据的散列值H和计算获得的散列值,如果两个散列值相同,则数据D是完整的;否则,数据D是修改过的数据。