密码学是一个庞大的领域,总体来说可将该领域中的加密方式分为2大类:
而今天介绍的hashlib模块是Python3中所独有的,提供了一系列的非对称加密算法:hash算法。
在Python2中hashlib模块被拆分成了md5模块和sha模块,它们提供的功能和Python3的hashlib模块相同。
官方文档
以下是该模块提供的部分常用方法及属性:
属性/方法 | 描述 |
---|---|
hashlib.algorithms_guaranteed | 以集合方式,列出所有平台所支持的hash算法 |
hashlib.algorithms_available | 以集合方式,列出当前所运行的Python解释器所支持的hash算法 |
hash.digest_size | 以字节表示结果hash对象的大小 |
hash.block_size | 以字节表示的hash算法的内部块大小 |
hash.name | 返回hash对象的规范名称 |
hash.copy() | 返回hash对象的拷贝副本 |
hash.update() | 在已有基础上对hash对象的内容进行更新 |
hash.hexdigest() | 返回16进制的字符串hash值 |
hash.digest() | 返回2进制的字节串hash值 |
Python的字典在键值对数据存储和读取时,就用到了hash算法。
比如:“k1” : "v1"的键值对在存储过程中,"k1"会通过hash()函数得出1个hash值,该hash值与v1一一对应,后续通过dict.get()方法通过"k1"找"v1"时,内部也是利用的这个hash值来进行查找。
通过字典的种种特性,我们可以顺势推导出hash的一些特性:
我们使用内置的hash()函数来验证这3点结论:
1)相同的内容求hash值,得到的hash结果也必然相同:
>>> hash("hello world") -484803057 >>> hash("HELLO WORLD") 264022494 >>> hash("hello world") -484803057
2)不能通过hash值反解出内容:
>>> hash("k1") -714364401 >>> hash("-714364401") 1936952577
3)如果采用相同的hash算法,无论需要校验的内容有多大,得到的hash值长度总是固定的:
>>> hash("hello") 313408759 >>> hash("hello, Python3") -1705693388
由于hash算法的特性,它常被用于一致性校验、密码存储等领域。
其中最著名的hash算法就是MD5,它被称为永不可破的hash算法,但随着技术的发展MD5已经不那么可靠了,它可以用撞库的方式对其进行反解。
而SHA256作为MD5的加强版,是目前的主流方案。
关于MD5和SHA家族的区别在于使用的加密算法不一样,以及它们生成的hash值长度不同:
如果你的项目安全等级较高,可采用SHA256作为加密方式,其他情况下使用MD5即可。
hashlib模块的使用非常简单,总体来说先要生成一个hash对象,然后再填入字节串即可。
首先是普通的使用,以MD5举例:
>>> import hashlib >>> m = hashlib.md5("hello world".encode("u8")) >>> m.digest() b'^\xb6;\xbb\xe0\x1e\xee\xd0\x93\xcb"\xbb\x8fZ\xcd\xc3'
如果对一个大字符串生成hash值,可使用update()方法在原有hash对象基础上进行内容更新:
>>> m = hashlib.md5() >>> m.update("line1".encode("u8")) >>> m.update("line2".encode("u8")) >>> m.update("line3".encode("u8")) >>> m.digest() b'\xcc\x0c\x81\xcd<\xfa)\x8e:\x06\x9c\xcal\x91\x9e\xdb
sha256的加密方式与md5的加密使用相同,如下所示:
>>> m = hashlib.sha256("hello world".encode("u8")) >>> m.digest() b"\xb9M'\xb9\x93M>\x08\xa5.R\xd7\xda}\xab\xfa\xc4\x84\xef\xe3zS\x80\xee\x90\x88\xf7\xac\xe2\xef\xcd\xe9"
基本使用就介绍完毕了,是不是非常简单呢?
在密码破解领域,有一个百试不爽的方法就是撞库破解。
撞库是指通过一个庞大的数据库来记录未加密字符串与加密后的值的一种映射关系,理论上来说只要这个数据库无限大,那么生成的hash值都能在这里找到其对应的生成字符串。
举个例子:
我现在有1个字符串,I LOVE YOU。
对他进行hash加密得到的结果假设为3242。
现在将这个对应关系放到数据库中,及3242这个hash值对应的字符串为I LOVE YOU。
如果有人要对3242进行反解,通过查询数据库即可知道结果。
这个思路非常的简单粗暴,但个人是不可能进行数据库的完善和搭建。
在Google上如果搜索MD5反解,应该能找到一些撞库网站,但大多数都是付费的,如果感兴趣可以试一试。
为了防止你的加密内容被撞库反解,我们可以使用加盐的策略来对已加密的内容进行二次加密。
整体思路如下,我们以一个普通的用户登陆作为案例:
理论很复杂,实操很简单。如下所示:
>>> salt = "slat".encode("u8") >>> userPwd = "123456".encode("u8") >>> hashObject = hashlib.md5(salt) # ❶ >>> hashObject.update(userPwd) # ❷ >>> savePwd = hashObject.digest() # ❶ >>> savePwd b'ELr\x05\x14$z=\x1d\x19(^4L>n' >>> >>> >>> reLoginPwd = "123456".encode("u8") >>> hashObject = hashlib.md5(salt) # ❶ >>> hashObject.update(reLoginPwd) # ❷ >>> getPwd = hashObject.digest() # ❸ >>> getPwd == savePwd # ❹ True
❶:加盐
❷:加入用户内容
❸:获得存储密码
❹:对比用户重登陆的密码hash值是否和以存储的密码hash值一致
在Server端对Client端发送文件的过程中,该文件可能被黑客截取做出一些篡改,如下所示:
server端 ---------> client端 | | 可能被黑客窃取,修改下载文件
此时就需要使用文件校验来确保安全性了:
我们有2种方式,来进行文件校验的实现。
下面将采用模拟Server端生成文件校验hash值的整个过程。
首先是方式1,将文件所有内容hash校验一遍,安全系数最高,速度最慢。
res = "" m = hashlib.sha256() f = open(file="test.txt",mode="rb") while 1: temp = f.read(1024) # ❶ m.update(temp) # ❷ if not len(temp): f.close() hash_res = m.hexdigest() # ❸ break print(hash_res) # 48dd13d8629b4a15f791dec773cab271895187a11683a3d19d4877a8c256cb70
❶:更新hash值
❷:由于打开文件的模式是rb,故temp本身就是bytes类型,所以不用encode()
❸:当所有内容读取完毕后,生成文件的校验hash值
其次是方式2,文件指定指针点来更新hash值,安全系数小幅度降低,但速度大幅度提升。
迅雷等下载软件均采用此种方式,前提是要让用户知道我们seek()的文件指针点在哪里:
m = hashlib.sha256() f = open(file="1.txt",mode="rb") # ❶ f.seek(20,0) temp = f.read(10) m.update(temp) # ❷ f.seek(20,1) temp = f.read(10) m.update(temp) # ❸ f.seek(-20,2) temp = f.read(10) m.update(temp) # ❹ hash_res = m.hexdigest() print(hash_res) # daffa21b2be95802d2beeb1f66ce5feb61195e31074120a605421563f775e360
❶:在文件的开始位置,读取10个bytes,用作生成hash值的源内容部分
❷:在文件的中间位置,读取10个bytes,用作生成hash值的源内容部分
❸:在文件的末尾位置,读取10个bytes,用作生成hash值的源内容部分
❹:生成文件校验的hash值,该hash值共由30个bytes组成,分别来自文件的开始、中间、末尾位置。Ps:指针点越多,安全性越高,但是速度越慢
hmac模块的使用与hashlib大同小异。但是在某些方面会比hashlib更优秀:
它也是一个内置模块,以下是简单的使用:
>>> import hmac >>> hmacObject = hmac.new("hello world".encode("u8"), digestmod="md5") >>> hmacObject.update("salt".encode("u8")) >>> hashValue = hmacObject.digest() >>> hashValue b'\xf3Q\xff\xb2V{\x88\xfe\x0e\x9aX\x19\xbf\x12\xf3<' >>>
另外,还有一个compare_digest()方法,放入2个bytes类型,用于判断他们的值是否一致。