共计 1695 个字符,预计需要花费 5 分钟才能阅读完成。
通过哈希算法,我们可以验证一段数据是否有效,方法就是对比该数据的哈希值,例如,判断用户口令是否正确,我们用保存在数据库中的 password_md5
对比计算 md5(password)
的结果,如果一致,用户输入的口令就是正确的。
为了防止黑客通过彩虹表根据哈希值反推原始口令,在计算哈希的时候,不能仅针对原始输入计算,需要增加一个 salt 来使得相同的输入也能得到不同的哈希,这样,大大增加了黑客破解的难度。
如果 salt 是我们自己随机生成的,通常我们计算 MD5 时采用md5(message + salt)
。但实际上,把 salt 看做一个“口令”,加 salt 的哈希就是:计算一段 message 的哈希时,根据不同口令计算出不同的哈希。要验证哈希值,必须同时提供正确的口令。
这实际上就是 Hmac 算法:Keyed-Hashing for Message Authentication。它通过一个标准算法,在计算哈希的过程中,把 key 混入计算过程中。
和我们自定义的加 salt 算法不同,Hmac 算法针对所有哈希算法都通用,无论是 MD5 还是 SHA-1。采用 Hmac 替代我们自己的 salt 算法,可以使程序算法更标准化,也更安全。
Python 自带的 hmac 模块实现了标准的 Hmac 算法。我们来看看如何使用 hmac 实现带 key 的哈希。
我们首先需要准备待计算的原始消息 message,随机 key,哈希算法,这里采用 MD5,使用 hmac 的代码如下:
import hmac | |
message = b'Hello, world!' | |
key = b'secret' | |
h = hmac.new(key, message, digestmod='MD5') | |
# 如果消息很长,可以多次调用 h.update(msg) | |
h.hexdigest() | |
'fa4ee7d173f2d97ee79022d1a7355bcf' |
可见使用 hmac
和普通 hash 算法非常类似。hmac
输出的长度和原始哈希算法的长度一致。需要注意传入的 key 和 message 都是 bytes
类型,str
类型需要首先编码为bytes
。
练习
将上一节的 salt 改为标准的 hmac 算法,验证用户口令:
import hmac, random | |
def hmac_md5(key, s): | |
return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'MD5').hexdigest() | |
class User(object): | |
def __init__(self, username, password): | |
self.username = username | |
self.key = ''.join([chr(random.randint(48, 122)) for i in range(20)]) | |
self.password = hmac_md5(self.key, password) | |
db = {'michael': User('michael', '123456'), | |
'bob': User('bob', 'abc999'), | |
'alice': User('alice', 'alice2008') | |
} | |
def login(username, password): | |
user = db[username] | |
return user.password == hmac_md5(user.key, password) | |
# 测试: | |
assert login('michael', '123456') | |
assert login('bob', 'abc999') | |
assert login('alice', 'alice2008') | |
assert not login('michael', '1234567') | |
assert not login('bob', '123456') | |
assert not login('alice', 'Alice2008') | |
print('ok') |
小结
Python 内置的 hmac 模块实现了标准的 Hmac 算法,它利用一个 key 对 message 计算“杂凑”后的 hash,使用 hmac 算法比标准 hash 算法更安全,因为针对相同的 message,不同的 key 会产生不同的 hash。
