前言
哈希算法有时也被称作摘要算法,目的是将数据不可逆得转成一串固定长度的字符串。常用作密码保护等场景,如果我们只能拿到哈希计算之后的字符串,我们无法简单得推出他的原文。
目前来说,常用哈希算法有MD5、SHA1、SHA256、SHA512、SHA3等。
安全性
目单纯的md5和sha1已经不推荐用于加密场合了,因为使用的实在太频繁了。所以通过暴力破解(如彩虹表都可以破解出很大一部分密码了)
比特币目前用的是sha256算法
建议使用 PBKDF2, Argon2, Scrypt, Bcrypt 等方法 ,这些算法由于执行了多次的哈希,提高了暴力破解的难度,增加了安全性。
md5<<PBKDF2<Bcrypt<Scrypt
实用性
对于校验场合,md5一般都够用了,但是对于密码之类的敏感场合,请勿用md5,如果需要前后端都做哈希的话建议用PBKDF2+sha256,如果只做后端哈希的话可以考虑用Bcrypt。剩下的scrypt和argon2因为用的人太少不推荐。
加盐
一般的加盐实质就是在要加密的字符串前面或者后面加入一串随机字符串(hmac加盐不同)。这样就能把你的密码拉长增加安全性。而且推荐的做法是不同用户用不同的随机盐,这样就能防止诸如彩虹表的方法一次查表碰撞多个用户的密码。
md5 sha256 PBKDF2 Bcrypt实现示例
# -*- coding: utf-8 -*-
# @Time : 2021/4/13 2:11 下午
# @Author : meng_zhihao
# @Email : 312141830@qq.com
# @File : main.py
# 不同加密方式
import hashlib
import binascii
import bcrypt, time
import random
def md5(password):
# 1 即使加盐,也有被彩虹表破解的可能
# 2 有用被同一个md5的不同密码撞库的风险 (通过算法快速找到MD5(M1) = MD5(M2))
md = hashlib.md5(password) # 加盐减少彩虹表暴力破解风险(每个用户需要不同,这样就不能复用彩虹表) 加盐相当于在前面拼一截上去增加复杂度
# md.update(b'5y3w4h') # 加盐相当于拼到后面
password_encrypt = md.hexdigest()
return password_encrypt
def sha256(password): # 比特币目前采用这种 但是这里的加盐也是就拼接一下的,所以盐的长度一定要够长
s = hashlib.sha256() # Get the hash algorithm.
s.update(b's')
s.update(password) # Hash the data.
b = s.hexdigest() # Get he hash value.
return b
def sha512(password): # 更长,更安全
s = hashlib.sha512() # Get the hash algorithm.
s.update(password) # Hash the data.
b = s.hexdigest() # Get he hash value.
return b
def gen_salt(len):
rs = ''
strs = 'abcdefghijklmnopqrstuvwxyz0123456789'
for i in range(len):
rs += random.choice(strs)
return rs
# PBKDF2
def pbkdf2(password): # 这个算法是把每次的结果当初盐值和原来的password继续算,然后把多次的结果取异或 前端做10000次需要0.5s,然而后端只需要0.0043s,差了100倍
# 主要通过的是迭代来增加时长
start = time.time()
x = hashlib.pbkdf2_hmac("sha256", password, b"mysalt", 10000, dklen=32) # 相同盐值,不同迭代次数 digestLength
# 这算法能生成很多长度的结果,所以要约定最后结果字节数
# 密钥被提取为最终哈希的前dkLen byte,这就是为什么存在大小限制的原因。
print("x_3 = " + binascii.hexlify(x).decode())
# 要防止暴力破解还是得每个用户一个盐值,并且配上合适的迭代次数(迭代次数建议和用户相关。。。)
end = time.time()
t = end - start
print(t)
def Bcrypt(password): # 比较适合密码,但是这校验不太对。。居然要密码原文
salt = bcrypt.gensalt()
print(salt)
hash = bcrypt.hashpw(password, salt) # 数据库存这个
hash1 = bcrypt.hashpw(password, hash) # 新密码
print(hash, hash1) # 生成的密码中,$是分割符,无意义;2a是bcrypt加密版本号;10是cost的值(默认值);而后的前22位是salt值;再然后的字符串就是密码的密文了
# 特点是每次结果里带了盐值 好处是可以逐步增大work factor,而且不会影响已有用户的登陆
# 速度调整 No argument (Default) - 0.192 ms:
start = time.time()
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
end = time.time()
t = end - start
print(t)
# Work factor of 14 - 0.755 ms:
start = time.time()
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=14)) # 成本稀疏就是2的多少次方
end = time.time()
t = end - start
print(t)
def Scrypt():
# 这个主要是对抗矿机的,需要内存和cpu更长
pass
def print_hex(bytes):
l = [hex(int(i)) for i in bytes]
print(" ".join(l))
# 建议的盐值都很长很长
if __name__ == '__main__':
password = b'aaaaaa'
# print_hex(password)
result = pbkdf2(password)
# result = Bcrypt(password)
print(result)