摘要算法(哈希算法、散列算法)
摘要算法简介
摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
Python的hashlib提供了常见的摘要算法,如 MD5,SHA1,SHA512 等等。
提示:
要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。
如果原文内容是一个字符串'I am Jason',经MD5计算内容摘要是'2d3ec0dd5d4b99a2c5f1eb47656637e0'。如果有人篡改了你的文章,并发表为'I am Ross',篡改后的字符串计算出的摘要('6845af67ef35bfe261f6fed5a66ff3ab')不同于原文的摘要,你就知道该内容被串改了。
摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest。
MD5 摘要算法示例
MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。
数据量小的时候可以一次性求出摘要
import hashlib
md5 = hashlib.md5()
# update中的数据需要是bytes类型的数据据。
md5.update(bytes('I am Jason', encoding='utf-8'))
# digest()返回的是二进制的字符串表达形式
data1 = md5.digest()
# hexdigest()返回的是十六进制的字符串表达形式
data2 = md5.hexdigest()
print(data1) # b'->\xc0\xdd]K\x99\xa2\xc5\xf1\xebGef7\xe0'
print(data2) # 2d3ec0dd5d4b99a2c5f1eb47656637e0
数据量很大,可以分块多次调用update(),最后计算的结果是一样的:
import hashlib
md5 = hashlib.md5()
md5.update(bytes('I am ', encoding='utf-8'))
md5.update(bytes('Jason', encoding='utf-8'))
data = md5.hexdigest()
print(data) # 多次求值后返回的是十六进制的字符串表达形式还是 2d3ec0dd5d4b99a2c5f1eb47656637e0
SHA1 摘要算法示例
另一种常见的摘要算法是SHA1,调用SHA1和调用MD5完全类似, SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。
SHA1 同样可以将数据分多段计算摘要
import hashlib
md5 = hashlib.sha1()
md5.update(bytes('I am ', encoding='utf-8')) # update中的数据需要是bytes类型的数据据。
md5.update(bytes('Jason', encoding='utf-8')) # update中的数据需要是bytes类型的数据据。
data = md5.hexdigest()
print(data) # 返回的是十六进制的字符串表达形式 c0f965c41ef25423c2fbbcd05dfc767b04c9ba7f
算法SHA256和SHA512比SHA1更安全,不过越安全的算法不仅越慢,而且摘要长度更长。
碰撞
有没有可能两个不同的数据通过某个摘要算法得到了相同的摘要?完全有可能,因为任何摘要算法都是把无限多的数据集合映射到一个有限的集合中,这种情况称为碰撞。
比如:一个字符串'do you have the same hash value with me?' 的 md5 摘要为 b9455cde6391d136c33541b7293ec394
, 很有可能另一个字符串的md5摘要也是这个值。
摘要算法应用
摘要算法一个常用场景就是数据库存储用户密码的存储。
如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入黑客的手里。此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令
正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要,比如MD5:
username | password |
---|---|
user01 | e10adc3949ba59abbe56e057f20f883e |
user02 | 878ef96e8614558c38c87f0410ad1539 |
user03 | 99b1c2188db85afee403b1536010c2c9 |
当用户登录时,首先计算用户输入的明文口令的MD5,然后和数据库存储的MD5对比,如果一致,说明口令输入正确,如果不一致,口令肯定错误。
下面是一个简单的代码演示
import hashlib
db = {
'user01': '202cb962ac59075b964b07152d234b70', # 123
'user02': '289dff07669d7a23de0ef88d2f7129e7', # 234
'user03': 'd81f9c1be2e08964bf9f24b15f0e4900', # 345
}
def check():
while 1:
username = input('username: >>>').strip()
passwd = input('password: >>>').strip()
md5 = hashlib.md5()
md5.update(passwd.encode(encoding='utf-8'))
pwd_md5 = md5.hexdigest()
if username in db and pwd_md5 == db[username]:
print('OK')
else:
print('BAD')
check()
MD5 安全性
采用MD5存储口令也不一定安全。因为如果一个黑客已经拿到了存储MD5口令的数据库,可以通过暴力破解的方式反推用户的明文口令(当然真正的黑客不会这么傻)。黑客可以事先计算出一些常用口令的MD5值,得到一个反推表(彩虹表),然后逐个碰撞即可(不一定所有的都可以成功)。
md5值 | 密码 |
---|---|
202cb962ac59075b964b07152d234b70 | 123 |
289dff07669d7a23de0ef88d2f7129e7 | 234 |
d81f9c1be2e08964bf9f24b15f0e4900 | 345 |
这样,无需破解,只需要对比数据库的MD5,黑客就获得了使用常用口令的用户账号。
对于用户来讲,当然不建议使用过于简单的口令。此外,我们还可以在程序设计上对简单口令加强保护。这就是下文要提到的加盐。
Salt 加盐
由于常用口令的MD5值很容易被碰撞出来,所以,存储的用户口令不再是那些已经被计算出来的常用口令的MD5,我们可以通过对原始口令加一个复杂字符串来实现混淆,俗称 "加盐":
经过Salt处理的MD5口令,只要Salt不被黑客知道,即使用户输入简单口令,也很难通过MD5反推明文口令。
加盐可以有两种方法:
方法1
salt = b'xxx'
md5 = hashlib.md5(salt)
md5.update('your password'.encode('utf-8'))
md5.hexdigest()
方法2
salt = b'xxx'
md5 = hashlib.md5()
md5.update(b'your password' + salt)
md5.hexdigest()
import hashlib
db = {
'user01': 'cb2921a386719d7467412b5573973529', # 123 salt=b'xxx';
'user02': '8d36de04ecc00c605caf4b2798328a59', # 234 salt=b'xxx';
'user03': '658d38b0b92c8e7e5eab2bef72b539c7', # 345 salt=b'xxx';
}
def check():
while 1:
username = input('username: >>>').strip()
passwd = input('password: >>>').strip()
md5 = hashlib.md5()
md5.update(passwd.encode(encoding='utf-8') + b'xxx')
pswdmd5 = md5.hexdigest()
if username in db and pswdmd5 == db[username]:
print('OK')
else:
print('BAD')
check()
关于相同口令的MD5值的问题
但是如果有两个用户都使用了相同的简单口令比如123456,在数据库中,将存储两条相同的MD5值,这说明这两个用户的口令是一样的。
为了让使用相同口令的用户存储不同的MD5,我们假定用户无法修改登录名,就可以通过把登录名作为Salt的一部分来计算MD5,从而实现相同口令的用户也存储不同的MD5。
import hashlib
salt = 'xxx'
username = user01
password = 123
md5 = hashlib.md5()
md5.update(username + password + salt)
data = md5.hexdigest()
print(data)