Hash function 在网络安全里是个无处不在的概念——存密码、verify 文件、git commit、区块链——都建在它上面。但它确实是密码学里最反直觉的概念之一。我用 5 层讲透,从最具象到最抽象。


第 1 层:用一个生活类比开始

想象你在做一道菜——比如红烧肉。

你把所有食材(五花肉、酱油、糖、八角、姜……)一起放进锅里,炖 2 小时。最后端上桌——这就是一道红烧肉。

现在问你两件事:

  • 问题 1:给定食材 → 能不能做出红烧肉? → 可以,跟着步骤做就行。
  • 问题 2:给定一盘做好的红烧肉 → 能不能还原出”用了多少肉、多少酱油、多少糖”? → 几乎不可能。你尝得出咸度,但说不出确切克数。倒过来推每一种原料各多少,做不到。

Hash function 就是这个”做菜”过程的数学版:

现实数学
食材(五花肉 / 酱油 / 糖)数据(任何文件、字符串、密码)
做菜步骤(炖 2 小时)Hash 算法(SHA-256、MD5)
那盘做好的菜Hash 值(那一串十六进制字符)

核心特性:做容易,反向推不出来。


第 2 层:看一眼真实的 hash 长什么样

我让你看几个具体例子,你立刻有手感:

输入: "hello"
SHA-256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

输入: "Hello"   ← 只改了第一个字母大小写
SHA-256: 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969

输入: "hello world"
SHA-256: b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9

输入: "hello"  ← 跟第一个一模一样
SHA-256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

你看到了什么?

  • 观察 1 —— 每个 hash 长度都一样(64 个字符)。不管你输入”hello”还是输入整本《红楼梦》,SHA-256 输出永远是 64 个字符。
  • 观察 2 —— helloHello 只差一个字母大小写,hash 完全不一样。不是改 1/5,是 100% 不一样。
  • 观察 3 —— 同样的输入 hello,永远产生同样的 hash。这不是随机,是确定的
  • 观察 4 —— 看 hash 你猜不出原文。你看到那一串 2cf24dba...,没有任何线索告诉你原文是”hello”。

第 3 层:hash 的 5 个特性(套到”菜”上)

#特性数学定义红烧肉类比
1确定性同样输入永远同样输出同样食材 + 同样步骤,永远做出同样味道
2单向性从 hash 反推原文 → 做不到给你菜,推不出每种食材的精确克数
3雪崩效应输入改一点点 → 输出完全不同多放一勺盐 → 整盘菜味道全变
4固定长度不论输入多长,输出长度固定不管放多少食材,最后都是一盘菜
5抗碰撞两个不同输入产生同 hash → 几乎不可能(参见 hash collision)两道菜口味完全一致 → 几乎不可能

第 4 层:这玩意儿到底有什么用

Hash function 单看很奇怪——做容易做、反推不出来。这有啥用?

它有 3 个核心用途,你天天都在用,只是没意识到。

用途 1:验证文件没被篡改(完整性 = CIA 中的 I)

情景:你公司内网有个程序 invoice.exe,大家都用。某天攻击者偷偷换成了一个植入后门的版本。程序还能正常用,功能一模一样,但每次运行偷偷传数据出去。

你怎么知道程序被换了?

  • 直接看文件大小 —— 攻击者可以保持文件大小一样
  • 直接看文件名 —— 文件名根本没变
  • 打开看内容 —— exe 是二进制,看不出来

用 hash:

原版 invoice.exe → SHA-256 → A1B2C3D4...E5F6  (确定的 64 字符)
被改 invoice.exe → SHA-256 → 9X8Y7Z6W...3M2N  (完全不一样)

每周自动算一次 hash,跟存档的对比——只要变了,说明文件被改过。

这就是为什么 npm 安装包、Docker image、Linux 发行版下载页,永远都贴一个 SHA-256 值:

ubuntu-22.04.iso
SHA-256: a4acfda10b18da50e2ec50ccaf860d7f20b389df8765611142305c0e911d16fd

下载完后你自己算一遍 hash,和官方贴的对比。一样 = 没被人调包;不一样 = 立刻删了别用。

这就是 不可否认性 —— 你没法假装文件没改过,因为 hash 会出卖你。

用途 2:存密码(机密性 = CIA 中的 C)

这个用途比第一个更日常,但也更反直觉。

问题:网站需要验证你的密码,那密码到底怎么存?

❌ 错误方案 1:明文存

数据库:
shawn@auplus.com.au | 我的真密码123

→ 数据库被脱库 = 所有人密码全暴露。史诗级灾难。

❌ 错误方案 2:加密存

数据库:
shawn@auplus.com.au | AES加密(我的真密码123) | key=xxxx

→ 看起来安全。但攻击者也能拿到 key(他都进数据库了),然后全解开。 → 而且——网站根本不需要”知道”你的密码,只需要”验证”它。

✅ 正确方案:hash 存

数据库:
shawn@auplus.com.au | SHA-256("我的真密码123") = a1b2c3d4...

你登录的时候发生了什么:

1. 你在浏览器输入"我的真密码123"
2. 浏览器/服务器对这个输入计算 hash
3. 算出来的 hash = a1b2c3d4...
4. 跟数据库存的 hash 对比
5. 一样 → 让你进;不一样 → 拒绝

这套设计的天才之处:

  • 数据库永远不存原密码
  • 即使数据库被脱库,攻击者拿到的也只是 hash 值,不是密码
  • 攻击者反推不出原密码(单向性)
  • 但你下次登录还能正常验证(确定性 + 同输入 = 同输出)

网站从来不”知道”你的密码,但它能”验证”你的密码。 这就是 hash 的魔力。

这也解释了为什么:当你点”忘记密码”,网站让你”重设”,而不是”告诉你原密码是什么”——因为他们真的不知道。能直接告诉你原密码的网站,99% 是用明文存的,赶紧跑。

⚠️ 真实生产代码不是裸 SHA-256。为了防”彩虹表”等攻击,密码 hash 要加 salt(每个用户独立的随机字符串混进密码再 hash)+ 用 argon2 这类故意慢的算法。但这是另一个话题——本文讲的是 hash function 本身。

用途 3:数据指纹 / 版本标识

你天天用 git,每个 commit 都有一个 hash:

$ git log
commit a1b2c3d4e5f6...
    fix: 修复登录 bug
 
commit 9x8y7z6w5v4u...
    feat: 添加 MFA 支持

这个 hash 不是随机生成的——它是把整个 commit 的内容(代码 + 上一个 commit 的 hash + 时间 + 作者……)全部 hash 出来的

所以:

  • 任何人改一行代码 → commit hash 立刻变
  • 甚至改了 commit message → hash 也变
  • 整个 git 历史像锁链一样环环相扣——改动任何一个 commit,后面所有 commit 的 hash 都会变,被一眼识破

→ 这就是为什么 git 能保证版本可追溯、不可篡改。它的根基是 hash

(顺便,区块链就是这个原理扩大化——每个区块的 hash 包含前一个区块的 hash,串成不可篡改的链。)


第 5 层:加密 vs 哈希,最后一次区分

讲到这里,你应该能自己回答这个问题了:

加密和哈希有什么区别?

加密哈希
目的”存起来,有人能取出来”(给授权的人看)“存起来,谁动过都知道”(验证完整性 / 验证身份)
方向双向:加密 ↔ 解密单向:只能算出来,算不回去
用 key?✅ 用 key(对称一把,非对称两把)❌ 不用 key
保护 CIA 哪个字母?C(机密性)I(完整性)+ 间接保护 C(密码存储场景)
输出长度输出长度 ≈ 输入长度输出长度固定,跟输入无关
实际工具AES-256 / RSA / TLSSHA-256 / bcrypt / MD5

记忆口诀:

加密 = “可逆的锁,有钥匙能开” 哈希 = “不可逆的搅碎机,只能比对碎片”


看到 AI 给你写涉及 hash 的代码,1 个问题就能审

AI 替你写处理密码 / 验签 / 完整性校验的代码时,看到 hash 类调用先问:

它在做”算 hash”还是”对比 hash”?

  • 算 hash —— 输入数据 → 输出 hash 字符串,存起来
  • 对比 hash —— 同样的算法重算一遍,跟存的对比

一切都是这两件事的组合。看懂这一点,你看 AI 写的密码学相关代码就有了脚手架。


相关阅读


Hash 不是魔法,它就是”一锅炖好的菜”。 你能尝出味道,但说不出每种食材的克数——这朴素的不对称,撑起了密码学的半边天。