阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

hashlib

92次阅读
没有评论

共计 3942 个字符,预计需要花费 10 分钟才能阅读完成。

哈希算法简介

Python 的 hashlib 提供了常见的哈希算法,如 MD5,SHA1 等等。

什么是哈希算法呢?哈希算法又称摘要算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用 16 进制的字符串表示)。

举个例子,你写了一篇文章,内容是一个字符串 'how to use python hashlib - by Michael',并附上这篇文章的哈希是'2d73d4f15c0db7f5ecb321b6a65e5d6d'。如果有人篡改了你的文章,并发表为'how to use python hashlib - by Bob',你可以一下子指出 Bob 篡改了你的文章,因为根据'how to use python hashlib - by Bob' 计算出的哈希不同于原始文章的哈希。

可见,哈希算法就是通过哈希函数 hash(data) 对任意长度的数据 data 计算出固定长度的哈希digest,目的是为了发现原始数据是否被人篡改过。

哈希算法之所以能指出数据是否被篡改过,就是因为哈希函数是一个单向函数,计算 digest=hash(data) 很容易,但通过 digest 反推 data 却非常困难。而且,对原始数据做一个 bit 的修改,都会导致计算出的哈希完全不同。

我们以常见的哈希算法 MD5 为例,计算出一个字符串的 MD5 值:

import hashlib

md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?'.encode('utf-8'))
print(md5.hexdigest())

计算结果如下:

d26a53750bc40b38b65a520292f69306

如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:

import hashlib

md5 = hashlib.md5()
md5.update('how to use md5 in'.encode('utf-8'))
md5.update('python hashlib?'.encode('utf-8'))
print(md5.hexdigest())

试试改动一个字母,看看计算的结果是否完全不同。

MD5 是最常见的哈希算法,速度很快,生成结果是固定的 128 bit/16 字节,通常用一个 32 位的 16 进制字符串表示。

另一种常见的哈希算法是 SHA1,调用 SHA1 和调用 MD5 完全类似:

import hashlib

sha1 = hashlib.sha1()
sha1.update('how to use sha1 in'.encode('utf-8'))
sha1.update('python hashlib?'.encode('utf-8'))
print(sha1.hexdigest())

SHA1 的结果是 160 bit/20 字节,通常用一个 40 位的 16 进制字符串表示。

比 SHA1 更安全的算法是 SHA256 和 SHA512,不过越安全的算法不仅越慢,而且哈希长度更长。

有没有可能两个不同的数据通过某个哈希算法得到了相同的哈希?完全有可能,因为任何哈希算法都是把无限多的数据集合映射到一个有限的集合中。这种情况称为碰撞,比如 Bob 试图根据你的哈希反推出一篇文章'how to learn hashlib in python - by Bob',并且这篇文章的哈希恰好和你的文章完全一致,这种情况也并非不可能出现,但是非常非常困难。

哈希算法应用

哈希算法能应用到什么地方?举个常用例子:

任何允许用户登录的网站都会存储用户登录的用户名和口令。如何存储用户名和口令呢?方法是存到数据库表中:

name password
michael 123456
bob abc999
alice alice2008

如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入黑客的手里。此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令。

正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的哈希,比如 MD5:

username password
michael e10adc3949ba59abbe56e057f20f883e
bob 878ef96e86145580c38c87f0410ad153
alice 99b1c2188db85afee403b1536010c2c9

当用户登录时,首先计算用户输入的明文口令的 MD5,然后和数据库存储的 MD5 对比,如果一致,说明口令输入正确,如果不一致,口令肯定错误。

练习

根据用户输入的口令,计算出存储在数据库中的 MD5 口令:

def calc_md5(password):
    pass

存储 MD5 的好处是即使运维人员能访问数据库,也无法获知用户的明文口令。

设计一个验证用户登录的函数,根据用户输入的口令是否正确,返回 True 或 False:

db = {'michael': 'e10adc3949ba59abbe56e057f20f883e',
    'bob': '878ef96e86145580c38c87f0410ad153',
    'alice': '99b1c2188db85afee403b1536010c2c9'
}

def login(user, password):
    pass

# 测试:
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')

采用 MD5 存储口令是否就一定安全呢?也不一定。假设你是一个黑客,已经拿到了存储 MD5 口令的数据库,如何通过 MD5 反推用户的明文口令呢?暴力破解费事费力,真正的黑客不会这么干。

考虑这么个情况,很多用户喜欢用 123456888888password 这些简单的口令,于是,黑客可以事先计算出这些常用口令的 MD5 值,得到一个反推表:

hash_to_plain = {'e10adc3949ba59abbe56e057f20f883e': '123456',
    '21218cca77804d2ba1922c33e0151105': '888888',
    '5f4dcc3b5aa765d61d8327deb882cf99': 'password',
    '...': '...'
}

这样,无需破解,只需要对比数据库的 MD5,黑客就获得了使用常用口令的用户账号。

对于用户来讲,当然不要使用过于简单的口令。但是,我们能否在程序设计上对简单口令加强保护呢?

由于常用口令的 MD5 值很容易被计算出来,所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的 MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”:

def calc_md5(password):
    return get_md5(password + 'the-Salt')

经过 Salt 处理的 MD5 口令,只要 Salt 不被黑客知道,即使用户输入简单口令,也很难通过 MD5 反推明文口令。

但是如果有两个用户都使用了相同的简单口令比如123456,在数据库中,将存储两条相同的 MD5 值,这说明这两个用户的口令是一样的。有没有办法让使用相同口令的用户存储不同的 MD5 呢?

如果假定用户无法修改登录名,就可以通过把登录名作为 Salt 的一部分来计算 MD5,从而实现相同口令的用户也存储不同的 MD5。

练习

根据用户输入的登录名和口令模拟用户注册,计算更安全的 MD5:

db = {}

def register(username, password):
    db[username] = get_md5(password + username + 'the-Salt')

然后,根据修改后的 MD5 算法实现用户登录的验证:

import hashlib, random

class User(object):
    def __init__(self, username, password):
        self.username = username
        self.salt = ''.join([chr(random.randint(48, 122)) for i in range(20)])
        self.password = get_md5(password + self.salt)

db = {'michael': User('michael', '123456'),
    'bob': User('bob', 'abc999'),
    'alice': User('alice', 'alice2008')
}

def get_md5(user, pws):
    return ???

def login(username, password):
    user = db[username]
    return user.password == get_md5(user, 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')

小结

哈希算法在很多地方都有广泛的应用。要注意哈希算法不是加密算法,不能用于加密(因为无法通过哈希反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。

参考源码

use_hashlib.py

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-07发表,共计3942字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中