Python模块之hashlib模块

发布时间 2024-01-02 23:28:33作者: Lea4ning

hashlib模块

【一】概要

  • hashlib 模块是 Python 中提供对哈希算法的支持的模块。它提供了常见的哈希算法,如 MD5、SHA-1、SHA-224、SHA-256、SHA-384 和 SHA-512,以及一些其他哈希算法。

【二】常见用法

  1. 计算哈希值:
    • hashlib.md5(): 创建一个 MD5 哈希对象。
    • hashlib.sha1(): 创建一个 SHA-1 哈希对象。
    • hashlib.sha256(): 创建一个 SHA-256 哈希对象(也有其他 SHA 算法的对应方法)。
  2. 更新哈希对象:
    • update(data): 将字节数据更新到哈希对象中。
  3. 获取哈希值:
    • hexdigest(): 返回十六进制格式的哈希值。
    • digest(): 返回二进制格式的哈希值。
import hashlib
# 以md5算法创建哈希对象
md5 = hashlib.md5()
string = "hello world "
# 转为二进制数据
encode_str = string.encode(encoding='utf8')
# 将数据更新至哈希对象中
md5.update(encode_str)
# 输出十六进制格式的哈希值
print(md5.hexdigest())

【三】详解

【1】哈希算法

哈希算法是一种将任意长度的输入数据转换成固定长度的输出数据的算法。哈希算法的主要目的是为了快速、有效地查找数据,并确保数据的完整性。它具有以下特点:

  1. 固定长度输出: 无论输入数据的长度如何,哈希算法生成的哈希值(也称为摘要或散列值)的长度是固定的。
  2. 唯一性: 不同的输入数据应该映射到不同的哈希值。虽然理论上存在哈希冲突(不同的输入得到相同的哈希值),但好的哈希算法应该尽量避免冲突。
  3. 不可逆性: 从哈希值推导出原始输入数据应该是困难的,甚至是不可能的。这意味着无法通过哈希值反向计算得到原始数据。
  4. 高效性: 计算哈希值应该是高效的,即使输入数据很大。

哈希算法在许多领域中都有广泛的应用,包括密码学、数据完整性验证、数据结构设计(如哈希表)、数字签名等。常见的哈希算法包括 MD5、SHA-1、SHA-256、SHA-3 等。然而,由于一些哈希算法的安全性出现问题,如 MD5 和 SHA-1 已经被认为不再安全,因此在一些安全敏感的场景中,推荐使用更强大的哈希算法。

【1.1】哈希算法与加密算法

哈希算法与加密算法有本质的区别。

  1. 哈希算法: 主要用于生成固定长度的摘要或哈希值,是一种单向的、不可逆的转换。哈希算法的主要应用是数据完整性验证、数字签名、密码存储等。由于哈希算法是不可逆的,通常无法从哈希值还原出原始数据。
  2. 加密算法: 包括对称加密和非对称加密两种主要类型。对称加密使用相同的密钥进行加密和解密,而非对称加密使用一对公钥和私钥,其中公钥用于加密,私钥用于解密。加密算法是可逆的,通过逆向操作可以还原出原始数据

【2】摘要算法

  • 摘要算法(Digest Algorithm)是一种特殊的哈希算法,其主要目的是生成数据的固定长度摘要(也称为哈希值或散列值)。摘要算法具有与哈希算法相似的特性,但通常更强调在密码学和数据完整性验证等领域的应用。
  • 它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
  • 摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest
    • 目的是为了发现原始数据是否被人篡改过。
  • 摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数
    • 计算f(data)很容易,但通过digest反推data却非常困难。
    • 而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。

【3】"撞库"

【3.1】"撞库"

"撞库"通常是指通过破解或者攻击手段,获取到数据库中的用户账号和密码信息。这种情况通常发生在数据库的安全性受到威胁或者数据库存储的用户信息未经适当加密的情况下。

攻击者可能使用多种手段来尝试破解用户密码,其中包括:

  1. 暴力破解: 攻击者尝试使用各种可能的密码组合,直到找到正确的密码。这种方法需要大量的时间和计算资源,但在一些情况下可能成功。
  2. 字典攻击: 攻击者使用事先准备好的密码字典,其中包含常见的密码和短语,逐个尝试这些密码。这种攻击方式通常比暴力破解更高效。
  3. 彩虹表攻击: 这是一种使用彩虹表的攻击方式,彩虹表是预先计算出的密码哈希值与明文密码的对应关系。攻击者可以使用彩虹表来查找哈希值对应的原始密码。

【3.2】摘要算法的"撞库"

image-20240102222631942

  • 摘要算法的特点 :固定长度输出,并且当内容完全一致时,摘要算法的出来的值是一致的

  • 摘要算法的撞库指的是攻击者通过尝试不同的输入,生成相同摘要(哈希值)的情况。虽然摘要算法是单向函数,不可逆的特性使得无法直接从摘要还原出原始数据,但攻击者可以通过尝试大量不同的输入,直到找到一个与目标相同的摘要,从而实现对摘要算法的撞库攻击。

    • 网站里存储了大量的MD5的值.
      • 就像这样: 编号 | 明文 | 密文
      • 通过密文的查找 推导出 明文

img

【4】如何提高安全性

【4.1】MD5算法和SHA-256算法

MD5(Message Digest Algorithm 5)和SHA-256(Secure Hash Algorithm 256-bit)是两种常见的哈希算法,它们有一些特点和区别:

  1. 摘要长度:
    • MD5:产生128位(32个十六进制字符)的摘要。
    • SHA-256:产生256位(64个十六进制字符)的摘要。
  2. 碰撞安全性:
    • MD5:由于设计上的弱点,已经被证明对碰撞攻击不够安全,因为可以在合理的时间内找到两个不同的输入生成相同的MD5摘要。
    • SHA-256:提供更高级别的碰撞安全性,目前尚未发现有效的碰撞攻击方法。
  3. 性能:
    • MD5:相对较快,适用于一些简单的数据完整性检查和校验。
    • SHA-256:相对较慢,但在安全性方面更可靠,因此在对安全性要求较高的场景中更常用。
  4. 用途:
    • MD5:在一些对性能要求较高、对碰撞攻击风险不那么敏感的场景中仍然可以使用,例如校验文件完整性。
    • SHA-256:更常用于安全领域,如数字签名、证书生成等,以提供更高级别的安全性。

总体而言,由于MD5的弱点,SHA-256更常用于需要更高安全性的应用场景。在一些对碰撞攻击要求不太高的场景中,MD5可能仍然是一个合适的选择。

【4.2】MD5算法变种-截取位数

  • 如截取前10位
import hashlib

# 以md5算法创建哈希对象
md5 = hashlib.md5()
string = "hello world "
# 转为二进制数据
encode_str = string.encode(encoding='utf8')
# 将数据更新至哈希对象中
md5.update(encode_str)
# 输出十六进制格式的哈希值
ciphertext = md5.hexdigest()
# 得到哈希值的前10位
password = ciphertext[:10]
print(password)
# 60c08e2b03

image-20240102224458254

  • 这时,我们可以看到解密网站的输出结果已经出现了错误
  • 而内部验证时,只要根据特定的规则进行密文处理,再验证就可以得到正确的登录信息

【4.3】加盐

  • 在密码学中,"盐"(salt)是一种随机的、唯一的值,它被添加到密码之前进行哈希运算。盐的引入主要是为了增加密码的安全性,防止常见的攻击手段,如彩虹表攻击。

  • 具体而言,使用盐的目的有以下几点:

    • 唯一性: 盐是随机生成的,每个用户的盐应该是唯一的。这确保了即使两个用户使用相同的密码,由于其使用不同的盐,生成的哈希值也是不同的。

    • 防御彩虹表攻击: 彩虹表是一种预先计算好的密码哈希值的表格,攻击者可以使用它来快速查找常见密码的哈希值。通过使用盐,即使密码相同,由于每个用户使用不同的盐,攻击者也需要分别计算每个用户的哈希值,增加了攻击的难度。

    • 防御碰撞攻击: 如果没有盐,相同的密码将始终生成相同的哈希值,这可能导致碰撞攻击。通过为每个用户生成唯一的盐,即使密码相同,哈希值也是唯一的。

  • 在实际应用中,盐通常存储在数据库中,与用户的密码一起存储。在验证用户登录时,系统会从数据库中获取用户的盐值,将盐和用户输入的密码一起进行哈希运算,然后与存储的哈希值进行比较。这样的做法有效地增强了密码的安全性。

以md5举例
import hashlib

# 以md5算法创建哈希对象
md5 = hashlib.md5()
string = "123456"
salt = 'salt'
# 将"盐"和原来的密码拼接到一起,成为"123456salt"
string += salt
# 转为二进制数据
encode_str = string.encode(encoding='utf8')
# 将数据更新至哈希对象中
md5.update(encode_str)
# 输出十六进制格式的哈希值
ciphertext = md5.hexdigest()
print(ciphertext)  # 207acd61a3c1bd506d7e9a4535359f8a
#  不加盐 : e10adc3949ba59abbe56e057f20f883e
  • 加盐过后,对方哪怕撞库撞到了,得到的也是错误的数据
    • 如上述,对方得到明文"123456salt",对方并不清楚我加的是几位数的盐
    • 从来对数据的安全性有所提升

【4.4】确保数据库保密性

  • 社会工程学(Social Engineering)是一种攻击技术,它利用心理学和社会工作技巧来欺骗人们,使其执行某些特定的行为或透露敏感信息。这种攻击方法并不涉及技术漏洞,而是利用人类的天性和社会行为。社会工程学攻击的目标通常是获取机密信息、未授权访问系统或执行其他有害行为。

    社会工程学的方法包括但不限于以下几种:

    1. 欺骗和伪装: 攻击者可能伪装成信任的实体,如公司员工、客服代表、IT支持人员等,通过电话、电子邮件或面对面的方式向目标索取信息。
    2. 威胁和恐吓: 攻击者可能利用威胁、恐吓或胁迫来获取信息或执行某些操作。这可能包括威胁制裁、暴力或其他不良后果。
    3. 诱导和社交工程: 攻击者可能通过社交媒体等途径收集目标个人信息,然后利用这些信息进行欺骗。他们可能使用特定的社交技巧,如建立信任关系、制造紧急情况等。
    4. 媒体和信息收集: 攻击者可能通过研究目标的公开信息、媒体报道等途径,获取有关目标的详细信息,以便更好地进行欺骗。

    社会工程学攻击的成功通常依赖于攻击者的欺骗技巧和目标的社会工作技能。为了防范社会工程学攻击,个人和组织需要提高对潜在威胁的警觉性,进行培训以识别潜在的欺骗行为,并采取适当的安全措施来保护敏感信息。

【5】hashlib模块在python中的运用

【5.1】单次加密

import hashlib

md5 = hashlib.md5()
# 待加密的字符串
string = "helloworld"
string = string.encode(encoding='utf8')
'''转为二进制格式同样也可以通过 b'' 来引导 ,但是需要注意不可以转中文'''
'''中文的报错信息 {SyntaxError: bytes can only contain ASCII literal characters}'''
string_bin = b'helloworld'
md5.update(string)
# 二进制格式
print(md5.digest())  # b'\xfc^\x03\x8d8\xa5p2\x08TA\xe7\xfep\x10\xb0'
# 十六进制格式
print(md5.hexdigest())  # fc5e038d38a57032085441e7fe7010b0

  • 通常情况下,使用十六进制格式数据,因为易读写

【5.2】多次加密

  • 当需要加密的数据过长时,可以通过多次update 添加,效果与单词加密是一致的
import hashlib

md5 = hashlib.md5()
# 第一次update
md5.update(b'hello')
# 第二次update
md5.update(b'world')

# 二进制格式
print(md5.digest())  # b'\xfc^\x03\x8d8\xa5p2\x08TA\xe7\xfep\x10\xb0'
# 十六进制格式
print(md5.hexdigest())  # fc5e038d38a57032085441e7fe7010b0

【5.3】加盐

import hashlib

# 以md5算法创建哈希对象
md5 = hashlib.md5()
string = "123456"
salt = 'salt'
# 将"盐"和原来的密码拼接到一起,成为"123456salt"
string += salt
# 转为二进制数据
encode_str = string.encode(encoding='utf8')
# 将数据更新至哈希对象中
md5.update(encode_str)
# 输出十六进制格式的哈希值
ciphertext = md5.hexdigest()
print(ciphertext)  # 207acd61a3c1bd506d7e9a4535359f8a
#  不加盐 : e10adc3949ba59abbe56e057f20f883e
  • 存储数据时,可以将盐单独拿出来,也可以将盐拼接至密码的最后或开头
    • 为了安全性,建议与密码拼接至一起

【5.4】校验加密数据

  • 校验加密数据,同样利用数据的一致性,拿到设定的规则和盐,同样进行加密就可以得到一致的散列值
'''不加盐'''
import hashlib

# 哈希对象
md5 = hashlib.md5()

user_dict = {'user': 'user', 'password': 'fc5e038d38a57032085441e7fe7010b0'}
# 用户字典  # user : 用户名   # password : 加密后的密文

user_input = input("user:>>>")
pwd_input = input("pwd:>>>")
pwd_input = pwd_input.encode(encoding='utf8')
md5.update(pwd_input)
# 对输入的密码进行加密 得到密文
ciphertext = md5.hexdigest()
if ciphertext == user_dict['password']:
    print("登录成功")
else:
    print("登录失败")

'''加盐 ----- 单独存储了盐 '''
import hashlib

# 哈希对象
md5 = hashlib.md5()

user_dict = {'user': 'user', 'password': '207acd61a3c1bd506d7e9a4535359f8a'}
# 用户字典  # user : 用户名   # password : 加密后的密文

user_input = input("user:>>>")
pwd_input = input("pwd:>>>")
pwd_input = pwd_input.encode(encoding='utf8')
salt = b'salt'
md5.update(pwd_input)
md5.update(salt)
# 对输入的密码进行加密 得到密文
ciphertext = md5.hexdigest()
if ciphertext == user_dict['password']:
    print("登录成功")
else:
    print("登录失败")

import hashlib

md5 = hashlib.md5()
md5_old = hashlib.md5()
clear_txt = b'123456'
md5.update(clear_txt)
salt = 'salt'
password = salt + md5.hexdigest()
print(password)

user_dict = {'user': 'user', 'pwd': password}

user_input = input("user:>>>")
pwd_input = input("pwd:>>>")
pwd_input = pwd_input.encode(encoding='utf8')
md5_old.update(pwd_input)
# 对输入的密码进行加密 得到密文
ciphertext = md5_old.hexdigest()
# 通过切皮得到真正的密码数据
if ciphertext == user_dict['pwd'][4:]:
    print("登录成功")
else:
    print("登录失败")

【6】小练习

通过hashlib实现登陆注册中的密码加密

# 通过hashlib实现密码加密
# 登录注册
import json
import random
import hashlib


def get_username_pwd():
    username = input("请输入用户名:").strip()
    password = input("请输入密码:").strip()
    return username, password


# 加密密码函数
def encrypt_data(data):
    md5 = hashlib.md5()
    # 创建一个hash对象
    data = data.encode('utf8')
    # 对数据进行二进制处理
    md5.update(data)
    # 将数据进行加密
    encrypted_data = md5.hexdigest()
    # 16进制加密 # 传回32位的加密串
    return encrypted_data


# 获取随机验证码
def random_code(x):
    code = ''
    for i in range(x):
        random_list = [str(random.randint(0, 9)), chr(random.randint(97, 122)), chr(random.randint(65, 90)),
                       random.randint(0, 9)]
        code1 = str(random.choice(random_list))
        code += code1
    return code


# 将数据保存至json格式
def save_data(data, mode='a', path=None):
    try:
        with open(path, mode, encoding='utf8') as fp:
            # 为防止出现中文显示问题,设置ensure_ascii参数
            json.dump(data, fp, ensure_ascii=False)
    except Exception as e:
        print(f"意料之外的错误:{e}")


def read_data(path):
    with open(path, 'r', encoding='utf8') as fp:
        data_dict = json.load(fp)
        return data_dict


def login():
    username, password = get_username_pwd()
    # 拿到总的一个数据
    data_dict = read_data('user_pwd.json')
    # 拿到登录用户的信息
    user_dict = data_dict.get(username)
    if not user_dict:
        return print("用户不存在")
    # salt = user_dict['password'][-4:]
    # 拿到32位加密串
    true_password = user_dict.get('password')[:-4]
    # 将用户输入的密码进行同样的加密
    is_password = encrypt_data(password)
    if true_password == is_password:
        print("登录成功!")
    else:
        print("密码错误!")


def register():
    username, password = get_username_pwd()
    code = random_code(4)
    # 验证码校验
    code_input = input(f"请输入验证码:{code}\n验证码输入:>>>").strip()
    if code.upper() != code_input.upper():
        print("验证码输入错误!")
    # 加密密码
    password = encrypt_data(password)
    # 加入盐,提高密码安全度
    salt = random_code(4)
    password += salt
    data = read_data('user_pwd.json')
    # 将注册的用户信息加入至json文件
    data[username] = {'username': username, 'password': password}
    # json文件不可以出现多个字典及多行内容,将用户名作为键名,更新进原字典,并将原字典覆写
    save_data(data=data, mode='w', path='user_pwd.json')


def main():
    func_choice = input("请输入登录功能或注册功能:login / register >>>>>>")
    if func_choice not in ['login', 'register']:
        return print("请输入login / register !")
    elif func_choice == 'login':
        login()
    else:
        register()


main()