注:本文如涉及到代码,均经过Python 3.7实际运行检验,保证其严谨性。
本文阅读时间约为5分钟。
在解决散列表的冲突问题之前,我们先介绍完美散列函数。
什么是完美散列函数
给定一组数据项,如果一个散列函数能把每个数据项映射到不同的槽中,那么这个散列函数就可以称为完美散列函数。
对于固定的一组数据,总是能想办法设计出完美散列函数。但是,如果数据项经常性地变动,很难有一个系统性的方法来设计对应的完美散列函数。此时就会出现冲突(collision)。
当然,冲突也不是致命性的错误,同样有办法处理。
获得完美散列函数的一种方法是,扩大散列表的容量,大到所有可能出现的数据项都能够占据不同的槽。但是,很显然,这种方法在数据项范围过大的情况下不太实用,会浪费太多存储空间,这种代价太大。
所以,我们采用折中的方案。好的散列函数需要具备特性:
- 冲突最少(近似完美)。
- 计算难度低(额外开销小)。
- 充分分散数据项(节约空间)。
完美散列函数的更多用途
除了用于在散列表中安排数据项的存储位置,散列技术还用在信息处理的很多领域。
由于完美散列函数能够对任何不同的数据生成不同的散列值,如果把散列值当作数据的“指纹”或者“摘要”,这种特性被广泛应用在数据的一致性校验上。
由任意长度的数据生成固定长度的“指纹”,并要求具备唯一性,这在数学上是无法做到的。但是,设计巧妙的“准完美”散列函数却能在实用范围内做到这一点。
作为一致性校验的数据“指纹”函数需要具备以下特性:
- 压缩性——任意长度的数据,得到的“指纹”长度是固定的。
- 易计算性——丛原数据计算“指纹”很容易;从指纹计算原数据是不可能的。
- 抗修改性——对原数据的微小改动,都会引起“指纹”的大改变。
- 抗冲突性——已知原数据和“指纹”,要找到相同指纹的数据(伪造)是非常困难的。
散列函数MD5/SHA
最著名的近似完美散列函数是MD5和SHA系列函数。
MD5(Message Digest)将任何长度的数据变换为固定长度为128位(16字节)的“摘要”。
128位二进制已经是一个极为巨大的数字空间:据说是地球沙粒的数量级别。
SHA(Secure Hash Algorithm)是另一组散列函数。
SHA-0/SHA-1输出散列值160位(20字节)。
SHA-256/SHA-224分别输出256位、224位。
SHA-512/SHA-384分别输出512位和384位。
160位,相当于10的48次方,地球水分子数量估计是10的47方。
256位二进制相当于10的77次方,已知宇宙所有基本粒子大约是10的72次方到87次方之间。
虽然近年来发现MD5/SHA-0/SHA-1三种散列函数能以极其特殊的情况来构造个别碰撞(散列冲突),但在实际应用中从未有过实际的威胁。
Python的散列函数库hashlib
Python自带MD5和SHA系列的散列函数库:hashlib,该库包括了md5/sha1/sha224/sha256/sha384/sha512等6种散列函数。
import hashlib
# 128位散列值。
hashlib.md5("Hello, World!".encode('utf-8')).hexdigest()
Out[6]: '65a8e27d8879283831b664bd8b7f0ad4'
# 160位散列值。
hashlib.sha1("Hello, World!".encode('utf-8')).hexdigest()
Out[7]: '0a0a9f2a6772942557ab5355d76af442f8f65e01'
无论多么长的字符串,只要使用同一散列函数库,返回的位数都是固定的。如下所示:
hashlib.md5("Are you OK?".encode('utf-8')).hexdigest()
Out[9]: 'eb48ea947c28cb870b638855e2bdbcca'
hashlib.md5("Don't be a cutie pie!".encode('utf-8')).hexdigest()
Out[10]: 'a14615484f10095a3f1af379f7dc7d50'
hashlib.md5("No one knows the new coronavirus better than me.".encode('utf-8')).hexdigest()
Out[11]: 'e4fe757217d4bda4aedb95790608a632'
hashlib.md5("没人比我更懂新冠病毒。".encode('utf-8')).hexdigest()
Out[12]: '90dd6c2d5bd6f67ebc42561031742785'
除了对单个的字符串进行计算之外,hashlib库还支持用update方法来对任意长的数据分部分来计算。无论多大的数据,都不会有内存不足的问题。如下所示:
m = hashlib.md5()
m.update("Hello, World!".encode('utf-8'))
m.update("This is part # 2!".encode('utf-8'))
m.update("This is part # 3!".encode('utf-8'))
m.hexdigest()
Out[20]: 'd9b68147bdb0945676799c26039ac4e8'
完美散列函数的应用
完美散列函数用于数据一致性校验,有以下应用:
- 数据文件一致性判断。
- 为每个文件计算其散列值,仅对比其散列值即可得知文件内容是否相同。
- 用于网络文件下载完整性校验。
- 用于文件分享系统:比如网盘中相同的文件可以无需存储多次。
- 加密形式保存密码。
- 仅保存密码的散列值,用户输入密码后,计算散列值并比对。
- 无需保存密码的明文即可判断用户是否输入了正确的密码。
- 防止文件篡改:原理同数据文件一致性的判断。防止篡改是电子商务的信息技术基础。
To be continued.