1.hashlib基础知识点
(1)hashlib提供了常用的摘要算法:SHA1, SHA224, SHA256, SHA384, SHA512, MD5。
(2)摘要算法:摘要算法又称为哈希算法、散列算法。他通过一个函数,把任意长度的数据转换成一个固定长度的数据串,通常来说是16进制的字符串来进行表示。
(3)摘要算法的目的:通过f()将任意长度data转换成固定长度digest,为了发现原始数据是否被人篡改过。
(4)摘要算法并不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改。
(5)摘要算法的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。
(6)两个不同的数据通过同一个摘要算法完全有可能得到相同的digest, 因为任何摘要算法都是把无限多的数据集合映射到一个有限的集合中。
2.MD5算法
(1)MD5算法是最常用的摘要算法,速度快,生成的是固定的128bit字节,通常用32位的16进制字符串表示。
(2)待摘要的数据量非常大,可多次update。
(3)常用方法:hashlib.md5(), hashlib.md5().update(string), hashlib.md5().digest()
import hashlib
m = hashlib.md5() # 创建md5对象
m.update(b"hello")
print(len(m.hexdigest()), m.hexdigest()) # 32 5d41402abc4b2a76b9719d911017c592
m.update("你好".encode(encoding="utf-8"))
print(len(m.digest()), m.digest()) # 16 b',\xfe6\x11f\x07\x8cYs\x0c\x07\\\x96k\xfe\x91'
# ------------------------------------------------------------------------------------------------------- #
# update(string): Update this hash object's state(散列对象的状态) with the provided string.
# digest(): Return the digest value as a string of binary data(二进制数据字符串).
# hexdigest(): Return the digest value as a string of hexadecimal digits(十六进制数据字符串).
# ------------------------------------------------------------------------------------------------------- #
3.SHA系列算法
(1)SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。
(2)SHA256和SHA512相比于SHA1更加的安全,不过越安全的算法越慢,而且摘要长度更长。
import hashlib
# SHA1
s1 = hashlib.sha1()
s1.update(b"hello")
print(len(s1.digest()), s1.digest()) # 20 b'\xaa\xf4\xc6\x1d\xdc\xc5\xe8\xa2\xda\xbe\xde\x0f;H,\xd9\xae\xa9CM'
print(len(s1.hexdigest()), s1.hexdigest()) # 40 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
s1.update("你好".encode(encoding="utf-8"))
print(len(s1.digest()), s1.digest()) # 20 b'AO>\xb5\xcfS[\xaa)N\xe4$.\xcf\xe4`\x1b!Rl'
print(len(s1.hexdigest()), s1.hexdigest()) # 40 414f3eb5cf535baa294ee4242ecfe4601b21526c
# SHA256:比较常用的
s256 = hashlib.sha256()
s256.update(b"hello")
print(len(s256.digest()), s256.digest()) # 32 b',\xf2M\xba_\xb0\xa3\x0e&\xe8;*\xc5\xb9\xe2\x9e\x1b\x16\x1e\\\x1f\xa7B^s\x043b\x93\x8b\x98$'
print(len(s256.hexdigest()), s256.hexdigest()) # 64 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
4.摘要算法应用
任何允许用户登录的网站都会存储用户登录的用户名和口令,一般采用的方法是将用户名和对应的口令存储到数据库的一张二维表中:
name passwd tom 123456 marry abc123 admin admin_maxin 如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入骇客的手里。此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令。所以,正确的保存口令的方式是存储口令的摘要:
name passwd tom e10adc3949ba59abbe56e057f20f883e marry e99a18c428cb38d5f260853678922e03 admin 20973b101799904f6e234f0c17969610 当用户登录时,首先计算用户输入的明文口令的MD5,然后和数据库存储的MD5对比,如果一致,说明口令输入正确,如果不一致,口令肯定错误。
考虑这么个情况,很多用户喜欢用
123456
,888888
,password
这些简单的口令,于是,骇客可以事先计算出这些常用口令的MD5值,得到一个反推表。因此,骇客只需要对比数据库的MD5,就获得了使用常用口令的用户账号。由于常用口令的MD5值很容易被计算出来,所以,要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”。
def calc_md5(password): return get_md5(passwd + "the-Salt")
经过Salt处理的MD5口令,只要Salt不被黑客知道,即使用户输入简单口令,也很难通过MD5反推明文口令。但是如果有两个用户都使用了相同的简单口令比如
123456
,在数据库中,将存储两条相同的MD5值,这说明这两个用户的口令是一样的。为了让使用相同口令的人在数据库二维表中存储不同的MD5,则可通过:
def register(username, passwd): db[username] = get_md5(passwd + username + "the-Salt")
# ! /usr/bin/env python
# coding:utf-8
# python interpreter:3.6.2
# author: admin_maxin
import hashlib
import datetime
import shelve
import time
# 设置登录超时时间
LOGIN_TIME_OUT = 0.60
# 设置临时存储用户“name”和“password”的shelve文件;允许写回
db = shelve.open("user_shelve", writeback=True)
# 新用户注册
def register():
name = None
# 对用户名的合法性进行检验
while True:
name = input("register name:").strip()
if name in db:
print("Name already exits!Please try again!")
continue
elif None == name:
print("The name cannot be empty!")
continue
elif "Q" == name:
return
else:
break
pwd = input("register passwd:").strip()
# 防止相同pwd的人存储相同的md5
db[name] = {"passwd": md5_digest(pwd + name), "last_login_time": time.time()}
# 判断当前用户“是否合法”和“是否超时”
def olduser():
name = None
pwd = None
passwd = None
while True:
name = input("name:").strip()
passwd = input("passwd:").strip()
# 判断当前用户“是否注册”
try:
pwd = db[name]["passwd"]
break
except AttributeError as ae:
print("\003[1;31;40mUsername '%s' doesn't existed\033[0m" % name)
break
except IndexError as ie:
print("\003[1;31;40mUsername '%s' doesn't existed\033[0m" % name)
break
# 判断密码摘要是否准确
if md5_digest(passwd + name) == pwd:
login_time = time.time()
last_login_time = db[name]["last_login_time"]
# 判断当前用户是否超过了登录时间
if login_time - last_login_time < LOGIN_TIME_OUT:
print("\033[1;31;40mYou already logged in at: <%s>\033[0m" % datetime.datetime.fromtimestamp(last_login_time).isoformat())
# 更新最近登录时间
db[name]["last_login_time"] = login_time
print("\033[1;32;40mwelcome back\033[0m", name)
else:
print("\033[1;31;40mlogin incorrect\033[0m")
# md5摘要传输近来的明文
def md5_digest(message):
m5 = hashlib.md5()
m5.update(message.encode(encoding="utf-8"))
return m5.hexdigest()
# 主界面
def menu():
prompt = """
(N)ew User Login
(E)xisting User Login
(Q)uit
Enter choice: """
# 设置程序退出标志
flg = False
while not flg:
choice = None
while True:
try:
choice = input(prompt).strip()[0].lower()
# 捕获异常选择直接变成选q退出程序
except (EOFError, KeyboardInterrupt):
print("\033[1;31;40m Error!\033[0m")
return
print("\nYou picked: [%s]" % choice) # 提示你的选择是什么
if choice not in "neq":
print("invalid option, try again")
continue
else:
break
if choice == "q":
flg = True
if choice == "n":
register()
if choice == "e":
olduser()
# 操作完成之后关闭文件句柄
db.close()
# 测试模块
if "__main__" == __name__:
menu()
5.彩虹表破解
彩虹表(rainbow table)是一个用于加密散列函数逆运算的预先计算好的表, 为破解密码的散列值(或称哈希值、微缩图、摘要、指纹、哈希密文)而准备。一般主流的彩虹表都在100G以上;这样的表常常用于恢复由有限集字符组成的固定长度的纯文本密码;这是空间 / 时间替换的典型实践, 比每一次尝试都计算哈希的暴力破解处理时间少而储存空间多,但却比简单的对每条输入散列翻查表的破解方式储存空间少而处理时间多;使用加salt的KDF函数可以使这种攻击难以实现。
6.hmac模块
python 还有一个 hmac 模块,它内部对我们创建 key 和 内容 再进行处理然后再加密。散列消息鉴别码,简称HMAC,是一种基于消息鉴别码MAC(Message Authentication Code)的鉴别机制。使用HMAC时,消息通讯的双方,通过验证消息中加入的鉴别密钥K来鉴别消息的真伪;一般用于网络通信中消息加密,前提是双方先要约定好key,就像接头暗号一样,发送方:key将消息加密,接收方:key + 消息明文再加密。拿加密后的值跟发送者的相对比是否相等,这样就能验证消息的真实性,及发送者的合法性了。
使用hmac算法比标准的hash算法更加的安全,因为针对相同的message,不同的key会产生不同的hash.
import hmac
key1 = "天王盖地虎"
msg = "你是250"
msg2 = "都是250"
send = hmac.new(key1.encode(encoding="utf-8"), msg.encode(encoding="utf-8"), digestmod="md5")
print(len(send.digest()), send.digest()) # 16 b'\x01\xa0m\r5EL\x9fp\\\xfe\xc6S\xb8Co'
print(len(send.hexdigest()), send.hexdigest()) # 32 01a06d0d35454c9f705cfec653b8436f
receive = hmac.new(key1.encode(encoding="utf-8"), msg2.encode(encoding="utf-8"), digestmod="md5")
print(receive == send) # False: 原始信息发生变化
# ------------------------------------------------------------------------------------------------------- #
# hmac.new():Create a new hashing object and return it
# key: The starting key for the hash.
# msg: if available, will immediately be hashed into the object's starting state.
# ------------------------------------------------------------------------------------------------------- #