hashlib是 Python 内置的哈希(散列)计算标准库,不需要额外安装,可以快速生成文本、文件的「哈希摘要」(也叫散列值、指纹)。它是密码存储、文件完整性校验、数据去重、防篡改等功能的底层基础,也是新手进阶必学的工具模块。
本文从最基础的「哈希是什么」讲起,全程配可运行代码 + 逐行解释 + 新手避坑指南,零基础也能完全吃透。
一、前置必懂:什么是哈希?
1. 通俗理解:哈希 = 数字指纹
你可以把哈希算法理解成一台「指纹生成机」:
- 输入:任意长度的数据(一段文字、一个文件、一张图片)
- 输出:固定长度的字符串(比如 32 位、64 位),就是这个数据的「哈希摘要 / 指纹」
就像每个人的指纹都是唯一的,只要原始数据有一点点改动,生成的指纹就会完全不同;相同的输入,永远会得到相同的指纹。
2. 哈希算法的四大核心特点
这四个特点是所有哈希应用的基础,新手一定要记牢:
- 单向不可逆:只能从原始数据算出哈希值,绝对不能从哈希值反推出原始数据(和加密解密不一样,加密是可逆的)
- 长度固定:不管输入是 1 个字符还是 10GB 的文件,同一种算法输出的哈希值长度永远一样(比如 MD5 永远是 32 位十六进制字符串)
- 雪崩效应:原始数据哪怕只改一个字、一个标点,生成的哈希值都会天差地别,完全看不出关联
- 确定性:相同的输入、相同的算法,无论算多少次,结果永远一模一样
3. hashlib 模块能做什么
Python 的hashlib封装了所有主流哈希算法,帮我们快速计算哈希值,常见用途:
- 文件完整性校验:下载软件 / 电影后,算 MD5 和官方对比,确认文件没被篡改、没下载损坏
- 密码安全存储:数据库里不存明文密码,只存密码的哈希值,就算数据库泄露也拿不到原密码
- 数据去重:给文件 / 内容算哈希,相同内容哈希一样,通过哈希快速判断重复
- 防篡改:接口签名、数据校验,确认数据传输过程中没被修改
二、基础使用:通用三步法
所有哈希算法的使用流程完全一致,记住三步即可:
- 创建哈希对象:选择算法,初始化一个计算对象
- 喂入数据:把要计算的二进制数据传进去(可以分批喂)
- 获取结果:生成最终的哈希摘要
1. 第一个入门例子:计算字符串的 MD5
# 1. 导入内置模块,无需安装 import hashlib # 2. 创建MD5算法的哈希对象 md5_obj = hashlib.md5() # 3. 喂入数据:必须传 bytes 字节类型,字符串要先用 encode 转成字节 # 重点:哈希是对二进制数据计算的,字符串不能直接传 text = "Hello Python" md5_obj.update(text.encode("utf-8")) # 4. 获取最终哈希摘要:hexdigest() 返回十六进制字符串(最常用) result = md5_obj.hexdigest() print(f"原始字符串:{text}") print(f"MD5 哈希值:{result}")运行结果:
原始字符串:Hello Python MD5 哈希值:a709c173228d32d242b7e38913a774a9逐行解释
hashlib.md5():创建一个专门用来算 MD5 的计算对象,类似准备好一台 MD5 指纹机.encode("utf-8"):把字符串转成二进制字节,这是新手 100% 会踩的坑 ——hashlib 所有 update 都只能传字节,传字符串会直接报错.update(数据):把数据喂给哈希对象,开始计算.hexdigest():输出最终的哈希值,格式是十六进制字符串,也是日常开发最常用的输出格式
2. 两种输出摘要的方式
表格
| 方法 | 返回类型 | 特点 | 适用场景 |
|---|---|---|---|
.hexdigest() | 十六进制字符串 | 可读性好,由 0-9 和 a-f 组成 | 绝大多数场景,存数据库、打印对比 |
.digest() | bytes 字节串 | 体积小,不可读 | 网络传输、二进制存储 |
示例对比:
import hashlib md5_obj = hashlib.md5(b"test") # 也可以直接在创建时传初始数据 print("十六进制:", md5_obj.hexdigest()) # 098f6bcd4621d373cade4e832627b4f6 print("字节格式:", md5_obj.digest()) # b'\t\x8fk\xcdF!\xd3s\xca\xdeN\x83&\'\xb4\xf6'3. 可以多次 update 分批喂数据
哈希支持增量计算:你可以分多次把数据喂进去,最终结果和一次性喂完全一样。这个特性非常重要,后面计算大文件会用到。
import hashlib md5_obj = hashlib.md5() # 分三次喂数据 md5_obj.update(b"Hello ") md5_obj.update(b"Python") # 结果和一次性喂"Hello Python"完全一样 print(md5_obj.hexdigest()) # a709c173228d32d242b7e38913a774a9三、常用哈希算法 & 示例
hashlib支持十几种算法,新手掌握最常用的 3 类即可,不同算法只是创建对象的函数名不一样,使用流程完全相同。
1. MD5 算法
- 输出长度:32 位十六进制字符串
- 特点:计算速度极快、体积小;但安全性低,已经被破解,存在碰撞风险(不同数据可能算出相同 MD5)
- 适用场景:非安全场景的文件完整性校验、普通数据去重,绝对不能用于密码、签名等安全场景
import hashlib def get_md5(text: str) -> str: """计算字符串的MD5值""" md5 = hashlib.md5(text.encode("utf-8")) return md5.hexdigest() print(get_md5("123456")) # e10adc3949ba59abbe56e057f20f883e2. SHA-1 算法
- 输出长度:40 位十六进制字符串
- 特点:比 MD5 稍慢,安全性也已经被攻破,不推荐用于安全场景
- 适用场景:旧系统兼容、普通文件校验
import hashlib sha1 = hashlib.sha1(b"Hello Python") print("SHA1值:", sha1.hexdigest()) # 输出:6b0d92ce83a04c7c40c57f9d9a3e0a3c2d7e3f0b3. SHA-2 系列(主流安全选择)
这是目前最主流、最安全的通用哈希算法,安全场景优先用这个系列,最常用的是 SHA256。
- SHA256:输出 64 位十六进制,安全性高、速度适中,绝大多数安全场景的首选
- SHA512:输出 128 位十六进制,安全性更高,速度稍慢,对安全要求极高的场景使用
SHA256 示例
import hashlib def get_sha256(text: str) -> str: """计算字符串的SHA256值""" sha256 = hashlib.sha256(text.encode("utf-8")) return sha256.hexdigest() print(get_sha256("123456")) # 输出:8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92算法对比总结表
| 算法 | 输出长度(十六进制) | 速度 | 安全性 | 推荐用途 |
|---|---|---|---|---|
| MD5 | 32 位 | 极快 | 低,已破解 | 普通文件校验、非安全去重 |
| SHA1 | 40 位 | 快 | 低,已破解 | 旧系统兼容、非安全校验 |
| SHA256 | 64 位 | 中等 | 高 | 接口签名、防篡改、安全校验 |
| SHA512 | 128 位 | 稍慢 | 极高 | 高安全级别的签名、校验 |
💡 新手选型建议:
- 只是做文件完整性、去重,追求速度 → 用 MD5
- 涉及安全、防篡改、签名 → 直接用 SHA256,不用纠结
- 存密码 → 都不要用,后面会讲原因和正确方案
四、核心特性演示:雪崩效应
哈希最经典的特性:原始数据哪怕只改一个字符,哈希值都会完全不一样,像雪崩一样。我们用代码直观验证一下:
import hashlib text1 = "今天天气真好" text2 = "今天天气真不好" # 只多了一个"不"字 md5_1 = hashlib.md5(text1.encode()).hexdigest() md5_2 = hashlib.md5(text2.encode()).hexdigest() print(f"原文1:{text1}") print(f"MD5:{md5_1}") print("-"*50) print(f"原文2:{text2}") print(f"MD5:{md5_2}")运行结果:
原文1:今天天气真好 MD5:a1b2c3...(示例) -------------------------------------------------- 原文2:今天天气真不好 MD5:x9y8z7...(示例)可以看到,只差了一个字,两个 MD5 没有任何相似之处,完全无法通过哈希值猜测原文的差异。这也是哈希能用来判断数据是否被篡改的核心原理。
五、进阶实战:计算大文件的哈希
计算小字符串可以一次性 encode,但如果是几个 GB 的大文件,一次性读进内存会直接导致内存溢出。这时候就要用到「分块读取 + 多次 update」的方式,边读边算,内存占用极低。
完整示例:计算文件的 MD5
import hashlib def get_file_md5(file_path: str) -> str: """ 计算大文件的MD5值,分块读取,不占内存 :param file_path: 文件路径 :return: MD5十六进制字符串 """ md5_obj = hashlib.md5() # 以二进制只读模式打开文件 with open(file_path, "rb") as f: # 每次读取 4KB 数据,循环读完整个文件 while True: chunk = f.read(4096) # 4096字节 = 4KB # 读完了就退出循环 if not chunk: break # 分批喂入哈希对象 md5_obj.update(chunk) return md5_obj.hexdigest() # 使用示例:计算当前目录下 test.txt 的MD5 if __name__ == "__main__": md5 = get_file_md5("test.txt") print(f"文件MD5:{md5}")代码解释
open(file_path, "rb"):必须用二进制模式rb打开文件,读出来的直接是字节,不需要再转码f.read(4096):每次只读 4KB,无论文件多大,内存永远只占 4KB,非常高效- 循环读完所有块,每次都
update,最终结果和一次性读完整文件计算完全一致
💡 拓展:想算 SHA256 只需要把
hashlib.md5()改成hashlib.sha256(),其他代码完全不用动。
六、经典应用场景实战
场景 1:文件完整性校验
下载文件后,对比官方给出的哈希值,确认文件没有损坏、没有被植入病毒。
import hashlib # 官方给出的正确MD5 official_md5 = "a1b2c3d4..." # 计算你下载的文件的MD5 your_file_md5 = get_file_md5("下载的安装包.exe") if your_file_md5 == official_md5: print("文件完整,没有被篡改") else: print("文件可能损坏或被篡改,请重新下载")场景 2:密码加盐哈希(原理演示)
很多新手会直接把密码的 MD5 存进数据库,这是非常危险的!
- 问题:简单密码的 MD5 可以通过「彩虹表」反查出来,比如
123456的 MD5 全网都能查到 - 解决方法:加盐(salt)—— 给密码拼接一段随机字符串,再算哈希,大幅提升破解难度
加盐哈希示例
import hashlib import uuid def hash_password(password: str, salt: str = None) -> tuple[str, str]: """ 给密码加盐生成哈希 :param password: 原始密码 :param salt: 盐,不传就自动生成随机盐 :return: (哈希值, 盐) """ # 生成随机盐:用uuid生成唯一随机字符串 if salt is None: salt = uuid.uuid4().hex # 32位随机十六进制字符串 # 密码 + 盐 拼接后算SHA256 sha256 = hashlib.sha256((password + salt).encode("utf-8")) password_hash = sha256.hexdigest() return password_hash, salt # ========== 注册时:生成哈希和盐,一起存进数据库 ========== pwd = "123456" pwd_hash, salt = hash_password(pwd) print(f"存储到数据库的哈希:{pwd_hash}") print(f"存储到数据库的盐:{salt}") # ========== 登录时:用同样的盐计算,对比结果 ========== def verify_password(input_pwd: str, saved_hash: str, saved_salt: str) -> bool: """验证密码是否正确""" # 用用户输入的密码 + 存好的盐,重新算哈希 calc_hash, _ = hash_password(input_pwd, saved_salt) # 和数据库里存的哈希对比 return calc_hash == saved_hash # 测试:输入正确密码 print(verify_password("123456", pwd_hash, salt)) # True # 测试:输入错误密码 print(verify_password("1234567", pwd_hash, salt)) # False⚠️ 非常重要的新手提醒: 上面只是原理演示!真实生产环境不要自己用 hashlib 写密码加密。 因为 MD5、SHA256 都是快哈希算法,计算速度太快,黑客可以每秒试几十亿次密码,暴力破解成本很低。 真正存密码请用专门的「慢哈希库」,比如
bcrypt、argon2-cffi,它们会故意放慢计算速度,大幅提升暴力破解成本。
场景 3:批量文件去重
给每个文件算哈希,相同文件哈希一定一样,通过哈希快速找出重复文件:
import os import hashlib def find_duplicate_files(folder_path: str) -> dict: """找出文件夹里的重复文件""" hash_map = {} # key: 文件哈希, value: 文件路径列表 for root, _, files in os.walk(folder_path): for file in files: file_path = os.path.join(root, file) file_md5 = get_file_md5(file_path) if file_md5 in hash_map: hash_map[file_md5].append(file_path) else: hash_map[file_md5] = [file_path] # 只返回有重复的 return {k: v for k, v in hash_map.items() if len(v) > 1}七、新手高频易错点总结
1. 直接传字符串报错
update()只能接收bytes字节类型,传字符串会报TypeError: Unicode-objects must be encoded before hashing。 ✅ 解决:字符串用.encode("utf-8")转成字节再传。
2. 以为哈希可以解密
哈希是单向的,只能正向计算,不能反向解密。网上所谓的「MD5 解密」都是彩虹表查询,本质是提前存好海量密码和对应的 MD5,碰运气匹配,不是真的解密。
3. 用 MD5 存密码
MD5 速度太快,容易被暴力破解,哪怕加了盐也不推荐用于密码存储。生产环境请使用专门的密码哈希库。
4. 大文件一次性读取
计算大文件哈希时不要一次性读进内存,一定要分块循环读取,避免内存溢出。
5. 混淆十六进制和字节摘要
日常打印、存数据库、对比都用hexdigest()的字符串格式;digest()只用于特殊的二进制传输场景。