Appearance
哈希(Hash)原理与常见应用
难度等级:🟡 中级(2-4年)
分类:工程化
归档日期:2026-09-03
📚 理论知识
一、什么是哈希
哈希是一种单向函数,把任意长度的输入(文字、文件等)转换成一段固定长度的"指纹"(哈希值)。
核心特性:
- 单向性:正向计算容易,反向推导原始数据几乎不可能(这是哈希和加密最本质的区别)
- 确定性:相同输入永远得到相同输出
- 雪崩效应:输入哪怕只改一个字符,输出也会完全不同
- 固定长度:无论输入多长,输出长度始终一致(如 SHA-256 恒为 256 位)
二、常见误解:密码"用哈希加密传输"
这个说法混淆了两件事,实际上分工是:
| 环节 | 使用技术 | 目的 |
|---|---|---|
| 密码从客户端传到服务器 | 加密(HTTPS/TLS,可逆) | 防止中间人窃听 |
| 密码在服务器中存储 | 哈希(如 bcrypt,不可逆) | 防止数据库泄露后密码被直接看到 |
加密 vs 哈希的本质区别:
| 加密 | 哈希 | |
|---|---|---|
| 是否可逆 | 可逆(有密钥可解密还原原文) | 不可逆,没有"解哈希"这回事 |
| 输出长度 | 通常与原文相关 | 固定长度 |
| 目的 | 保密,需要还原原文 | 生成指纹,用于比对、校验 |
| 例子 | AES、RSA、TLS | SHA-256、MD5、bcrypt |
正确表达应为「密码传输用加密,存储用哈希」,而不是「用哈希加密」——因为哈希本身设计上就不打算被还原,这是它的核心目的而非局限。
三、"固定 256 位"是什么意思
- 不管输入多长,SHA-256 的输出永远是 256 位(bit)
- 256 位 = 32 字节 = 十六进制表示下的 64 个字符(每个十六进制字符代表 4 位)
- 例:
8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c(正好 64 位字符)
由于任意长度的输入都要压缩进固定长度的输出,理论上存在哈希碰撞的可能(不同输入产生相同输出),但 2^256 的组合数极其庞大,实际概率可忽略不计。
四、文件是怎么哈希的
对哈希算法来说,文件本质上和字符串一样,都是一长串二进制字节,只是通常长得多。
计算过程:
- 读取文件的原始字节内容(不是"看到的文字",而是磁盘上的二进制数据)
- 哈希算法把数据切成固定大小的块(SHA-256 每块 512 位),逐块处理,并将结果不断"揉入"后续计算
- 最终输出固定长度(256 位)的哈希值,无论文件是 1KB 还是 10GB
典型用途:
- 文件完整性校验:下载文件后核对哈希值,判断是否被篡改或损坏
- 文件去重:云盘通过哈希判断文件是否已存在("秒传"的原理之一)
- 版本控制:Git 用哈希标识每次提交的内容
五、哈希计算耗时与文件大小的关系
计算时间与文件大小基本呈线性关系:文件越大,需要处理的"块"越多,耗时越长。
大致耗时参考(普通电脑,SHA-256):
| 文件大小 | 大致耗时 |
|---|---|
| 几十字节文本 | 微秒级 |
| 1MB 文档 | 毫秒级 |
| 1GB 视频 | 约 1~几秒 |
| 10GB 安装包 | 十几秒到几十秒 |
影响耗时的因素:
- 算法复杂度:MD5/SHA-1 较轻量但不安全;SHA-256/SHA-3 更安全但稍慢;bcrypt/Argon2 是故意设计得很慢(用于拖慢密码暴力破解)
- 硬件:现代 CPU 的专用指令集可大幅加速计算
- 实际瓶颈往往是文件读取的 I/O 速度,而非哈希计算本身
六、常见哈希算法分类
通用哈希算法(快,用于完整性校验):
| 算法 | 输出长度 | 安全性 |
|---|---|---|
| MD5 | 128位 | ❌ 已被攻破,不应用于安全场景 |
| SHA-1 | 160位 | ❌ 已被证明存在碰撞漏洞 |
| SHA-256 | 256位 | ✅ 主流安全选择(比特币、数字签名等) |
| SHA-3 | 可变 | ✅ 新一代标准,安全性更高 |
密码专用哈希算法(故意设计得慢):
- bcrypt、scrypt、Argon2
- 每次计算耗时几十到几百毫秒,目的是大幅拉高黑客批量破解密码的成本
- 存储密码的推荐做法:使用 bcrypt 或 Argon2,而非 SHA-256(SHA-256 太快,抗暴力破解能力较弱)
七、扩展:计算机存储单位换算
基础单位:
1 字节 (Byte, B) = 8 位 (bit, b)⚠️ 注意大小写:小写 b = 位,大写 B = 字节。网速常用 Mbps(兆位/秒),下载速度显示为 MB/s(兆字节/秒):
100 Mbps(网速) ÷ 8 = 12.5 MB/s(实际下载速度)二进制 vs 十进制换算差异:
| 单位 | 严格二进制(1024进位) | 日常十进制(1000进位,硬盘厂商常用) |
|---|---|---|
| KB | 1024 B | 1000 B |
| MB | 1024 KB | 1000 KB |
| GB | 1024 MB | 1000 MB |
| TB | 1024 GB | 1000 GB |
这也是为什么标称 1TB 的硬盘插入电脑后显示约 931GB——并非商家缺斤少两,而是十进制标称值除以二进制单位换算后的结果:
1,000,000,000,000 字节 ÷ 1024³ ≈ 931 GB完整单位链条:
1 字节 (B) = 8 位 (bit)
1 KB ≈ 1024 B(或 1000 B)
1 MB ≈ 1024 KB
1 GB ≈ 1024 MB
1 TB ≈ 1024 GB
1 PB ≈ 1024 TB常见误区
| 问题 | 要点 |
|---|---|
| 把哈希叫成"加密" | 哈希不可逆,没有"解哈希"这回事;加密是可逆的,两者目的完全不同 |
| 用 SHA-256 直接存密码 | SHA-256 计算太快,攻击者用彩虹表/GPU 暴力破解成本很低,密码存储要用故意设计得慢的 bcrypt/Argon2 |
| 认为哈希"绝对不会碰撞" | 理论上一定存在碰撞(鸽笼原理),只是 256 位空间下概率小到可忽略,MD5/SHA-1 已被证明能人为构造出碰撞 |
| 以为哈希输出长度和输入长度有关系 | 无论输入是 1 个字符还是 10GB 文件,同一算法输出长度恒定 |
❌ 错误答法示范
错误示例 1:「密码在数据库里是用哈希加密存的,需要的时候后端会把它解密出来做比对。」
❌ 问题:哈希不可逆,根本没有"解密"这一步。正确做法是把用户输入的密码用同样的算法(加盐)再哈希一次,拿两个哈希值做比对,而不是还原出明文密码。
错误示例 2:「哈希算法不会有两个不同的输入算出相同的输出,因为哈希值是唯一的。」
❌ 问题:输入空间是无限的,输出空间是有限的(256 位),根据鸽笼原理,碰撞在数学上必然存在,只是 SHA-256 这种安全算法找到一次碰撞的计算代价极其高昂,实际不可行;而 MD5、SHA-1 已经被证明可以在合理时间内构造出碰撞,所以才被判定为不安全。
🎤 模拟面试作答
嗯,这道题我从哈希是什么、和加密的区别、以及实际应用场景这几块来说。
哈希本质上是一个单向函数,把任意长度的输入压缩成固定长度的一段"指纹"。它有几个关键特性:单向不可逆,输入几乎不可能从哈希值反推出来;确定性,相同输入永远得到相同输出;还有雪崩效应,输入哪怕改一个字符,输出也会完全不一样,这个特性让它非常适合做完整性校验。
很多人容易把哈希和加密混为一谈,但这是两回事:加密是可逆的,有密钥就能解密还原出原文,用于保密传输,比如 HTTPS 用的 TLS;哈希是不可逆的,没有"解哈希"这个操作,目的是生成一个指纹用来比对,而不是还原数据。所以密码这块正确的说法应该是"传输用加密、存储用哈希",不能说"用哈希加密",因为哈希设计的初衷就是不能被还原,这是它的核心目的,不是局限性。
具体到密码存储,我们不会直接用 SHA-256 去存,因为它计算太快了,攻击者用 GPU 批量跑彩虹表或者暴力破解成本很低。业界推荐用 bcrypt 或者 Argon2 这类专门为密码设计的哈希算法,它们故意把单次计算做得很慢,比如几十到几百毫秒,正常登录场景用户感知不到,但会大幅拉高攻击者批量破解的成本。
哈希在其他场景也很常见,比如文件完整性校验——下载完文件核对哈希值判断有没有被篡改;云盘的秒传功能,本质上是拿文件哈希去查库里有没有相同的文件,有的话直接建立引用不用重新上传;Git 也是用哈希来标识每一次提交的内容。
另外哈希碰撞这个点也值得提一下:因为输入空间是无限的,输出空间是固定的 256 位,理论上碰撞是必然存在的,只是 SHA-256 这种安全算法要找到一次碰撞的计算量大到不可行;但像 MD5、SHA-1 已经被证明能在合理时间内人为构造出碰撞,所以现在都不建议用在安全相关的场景了。
……如果面试官感兴趣,我还可以聊聊加盐(salt)是怎么进一步防止彩虹表攻击的,或者展开讲讲 Git 具体是怎么用哈希组织提交历史的。
❓ 面试官追问预测
追问 1:光哈希密码就够安全了吗?如果两个用户密码一样,哈希值会不会一样,这样有什么问题?
应对思路:会一样,这正是纯哈希的隐患——攻击者可以预先生成一张"常见密码 → 哈希值"的彩虹表,拿数据库里泄露的哈希值直接反查。解决办法是加盐(salt):给每个用户的密码额外拼接一段随机字符串再哈希,盐值随哈希结果一起存起来,这样即使两个用户密码相同,加盐后的哈希值也不同,彩虹表直接失效。bcrypt 本身就内置了加盐机制。
追问 2:既然哈希碰撞理论上一定存在,那 Git 用哈希标识提交内容,会不会因为碰撞导致两个不同的提交被误判成同一个?
应对思路:理论上可能,但 Git 用的 SHA-1(新版本正迁移到 SHA-256)在 2^160 的空间下,随机碰撞的概率低到实际工程中可以忽略;真正的风险是"刻意构造"碰撞(比如 2017 年的 SHAttered 攻击针对 SHA-1),这也是社区推动 Git 迁移到 SHA-256 的原因之一。
追问 3:为什么 bcrypt 这种密码哈希要故意设计得慢,直接用 SHA-256 加盐不行吗?
应对思路:SHA-256 加盐确实能防彩虹表,但它计算速度极快(现代 GPU 每秒能算几十亿次),攻击者拿到哈希库后可以用暴力破解/字典攻击在短时间内跑完常见密码空间;bcrypt/Argon2 通过内置的"工作因子"把单次计算拖慢到几十到几百毫秒,正常登录用户感知不到延迟,但暴力破解的总耗时会被放大几个数量级,这是专门针对密码场景的权衡设计。
追问 4:文件去重/秒传功能,只用文件哈希判断"文件已存在"安全吗?会不会被人利用哈希碰撞伪造一个文件顶替别人的文件?
应对思路:理论上存在风险,如果只用哈希值当唯一标识且允许用户上传任意内容,攻击者构造出碰撞文件就可能覆盖或冒充别人的文件。工程上通常会用 SHA-256 这种目前没有已知实用碰撞攻击的算法,并且结合文件大小、上传者权限等额外校验,而不是完全只信任一个哈希值。
追问 5:哈希和 CRC32 这类校验和(checksum)有什么区别?
应对思路:CRC32 设计目的是快速检测随机比特错误(比如网络传输、存储介质的意外损坏),计算极快但不具备抗碰撞、抗篡改的安全属性,几行代码就能构造出 CRC32 相同的不同数据;哈希(尤其是 SHA-256 这类密码学哈希)设计目标包含抗碰撞、抗篡改,计算成本更高,用于安全校验、数字签名等场景。两者都能做"完整性校验",但抗恶意篡改的能力完全不在一个量级。