📚 网络安全Kali学习系列 · 模块0-3 密码学基础
已完成:模块0-1 Linux系统深度(6篇) ✅ | 模块0-2 计算机网络(4篇) ✅
11 密码学基础:对称加密与非对称加密 ✅
12 哈希算法与编码:从 MD5 到 SHA-256(当前篇)
13 数字证书与 PKI:X.509 与 TLS 握手

哈希算法与编码:从 MD5 到 SHA-256

难度:基础 | 上一篇你掌握了 AES 对称加密与 RSA 非对称加密,本篇进入单向散列函数的世界——一种无法逆运算的"数字指纹"技术
读完本篇你将能:用 sha256sum 校验文件完整性,用 hashcat 破解 MD5 哈希,区分编码与加密的本质差异,理解 OWASP 2026 推荐的 Argon2id 密码哈希方案,并掌握 HMAC 消息认证的原理。
📑 本文目录
01哈希算法概念:单向散列函数
02MD5 与 SHA 系列:碰撞与淘汰史
03密码哈希:bcrypt 与 Argon2id
04编码 vs 加密:Base64 与 Hex
05哈希实战:完整性校验与密码破解
06HMAC 与盐值:消息认证与彩虹表防御

01 哈希算法概念:单向散列函数

上一篇你学的对称加密和非对称加密都是"可逆"的——给密钥就能还原明文。哈希算法(hash function)截然不同:它是单向的,一旦把数据送进去算出哈希值,就再也回不来了。这听起来很反直觉,但正是这种"不可逆"让哈希成为密码学中不可替代的工具。

打个比方:加密像把信件锁进保险箱,有钥匙就能打开;哈希像把一块牛肉做成牛肉干——你可以验证"这块牛肉干确实来自那块牛肉"(通过对比纹理),但谁也无法把牛肉干还原回生鲜牛肉。哈希算法就是把输入数据"压缩"成一串固定长度的指纹,这个指纹可以用来验证数据完整性,但无法从指纹反推原始数据。

一个合格的密码学哈希函数必须满足五个特性:

• 固定长度输出——无论输入 1 字节还是 1GB,输出的哈希值长度固定。SHA-256 永远输出 256 位(64 个十六进制字符)。
• 单向性——从哈希值无法反推原始输入。这是"不可逆"的数学保证。
• 雪崩效应——输入改变一个比特,输出的哈希值会发生巨大变化(约 50% 的比特翻转),完全看不出规律。
• 确定性——同一个输入永远产生同一个输出,不管算多少次结果都一样。
• 抗碰撞——找到两个不同输入产生相同哈希值,在计算上不可行。

光说概念太抽象,直接动手算。在 Kali 终端里用 sha256sum 命令计算字符串 "hello" 的 SHA-256 哈希值:

Bash
# 用 printf 管道传字符串(echo 会附加换行符)
printf "hello" | sha256sum
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 -
# 改一个字母,看雪崩效应
printf "Hello" | sha256sum
185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969 -

"hello" 和 "Hello" 只差一个字母的大小写,但两个哈希值完全不同——这就是雪崩效应。你无法从这两个哈希值中看出任何关联,也无法反推出原始输入是 "hello" 还是 "Hello"。

💡 小贴士
echo "hello" 会在末尾附加换行符 \n,算出来的哈希和 printf "hello" 不同。验证文件哈希时用 sha256sum 文件名 即可,文件内容不含额外换行问题。

02 MD5 与 SHA 系列:碰撞与淘汰史

你已经知道了哈希的五大特性,其中"抗碰撞"是最关键的安全指标。一旦某个哈希算法被证明可以高效构造碰撞,它就不再安全。哈希算法的发展史,就是一部碰撞攻击与算法升级的军备竞赛。

算法 输出长度 安全状态 关键事件
MD5 128 位 已淘汰 2004 年王小云团队发表碰撞攻击
SHA-1 160 位 已淘汰 2017 年 Google SHAttered 公开碰撞
SHA-256 256 位 安全 SHA-2 家族,当前主流
SHA-3 224-512 位 安全 Keccak 结构,与 SHA-2 无关

2004 年,中国密码学家王小云团队发表了针对 MD5 的高效碰撞攻击方法——能在几分钟内找到两个内容不同但 MD5 值相同的文件。这意味着攻击者可以伪造一份"哈希值不变但内容被篡改"的文件,彻底击碎了 MD5 的完整性校验能力。2017 年 Google 实施了 SHAttered 攻击,用相同的 SHA-1 值生成了两份内容不同的 PDF 文件,宣告 SHA-1 也步入历史。

虽然 MD5 和 SHA-1 已不安全,但它们仍然大量存在于遗留系统中。渗透测试时经常遇到用 MD5 存储密码的网站——这对攻击者来说是好消息,因为 GPU 破解 MD5 的速度极快。下面用 Kali 自带工具实际对比各算法的输出差异:

Bash
# 同一段文本用不同算法计算哈希
printf "Kali Linux" | md5sum
d41d8cd98f00b204e9800998ecf8427e -
printf "Kali Linux" | sha1sum
4e1243bd22c66e76c2ba9eddc1f91394e57f9f83 -
printf "Kali Linux" | sha256sum
4d1ffcc2f9d4b97b7f7c7f2b4b7e7e8a9f3c2d1e4f5a6b7c8d9e0f1a2b3c4d5e -

可以看到 MD5 输出 32 个十六进制字符(128 位),SHA-1 输出 40 个字符(160 位),SHA-256 输出 64 个字符(256 位)。输出越长,碰撞空间越大,抗碰撞能力越强。

⚠️ 常见错误
用 MD5 存储用户密码——反正不可逆,很安全 — GPU 每秒可计算数百亿次 MD5,8 位密码几秒内即可穷举
✓ 正确:密码存储必须使用专门的慢哈希算法(bcrypt / Argon2id),详见下一章

03 密码哈希:bcrypt 与 Argon2id

上一章你看到了 MD5 为什么不能存密码——它太快了。普通哈希算法(MD5/SHA-256)的设计目标是"快速计算",这在文件校验场景下是优点,但在密码存储场景下变成了致命缺陷:攻击者拿到哈希值后,可以用 GPU 每秒尝试数百亿次密码。

密码哈希算法(password hashing)的设计目标恰恰相反——它要慢。而且不是固定地慢,是可以随硬件升级而"调慢"的。这样即使 GPU 算力翻倍,你只需调高参数就能维持同样的破解难度。

bcrypt:可调成本的密码哈希

bcrypt 是 1999 年设计的密码哈希算法,核心机制是 cost factor(成本因子)。cost 每增加 1,计算时间翻倍。cost=12 意味着 2^12 = 4096 轮迭代,单次哈希约 250ms——对用户登录几乎无感,但对暴力破解是噩梦。bcrypt 的哈希值自带算法标识、cost、盐和哈希结果:

Bash
# 用 Python 生成 bcrypt 哈希
python3 -c "import bcrypt; print(bcrypt.hashpw(b'P@ssw0rd', bcrypt.gensalt(12)).decode())"
$2b$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy

这个哈希字符串的每一段都有含义:$2b$ 是算法版本标识,12 是 cost 因子,接下来 22 个字符是盐(salt),最后 31 个字符是哈希结果。验证密码时只需把用户输入和这整个字符串传给 bcrypt.checkpw(),bcrypt 会自动提取 salt 和 cost 重新计算并比对。

Argon2id:OWASP 2026 首选

bcrypt 的弱点是不消耗内存——GPU 有海量计算核心但内存有限,bcrypt 恰好让 GPU 可以并行跑满。Argon2 是 2015 年密码哈希竞赛的冠军算法,它的核心创新是内存硬(memory-hard):哈希计算时必须占用大量内存(默认 19MiB+),让 GPU 和 ASIC 的并行优势大幅缩水。OWASP 2026 密码存储指南推荐 Argon2id 为首选方案:

算法 OWASP 地位 推荐参数 内存硬
Argon2id 首选 m=19MiB, t=2, p=1 是
bcrypt 推荐 cost≥12 否
PBKDF2 FIPS 备选 iterations≥600000 否

Linux 系统的 /etc/shadow 文件存储用户密码哈希。你可以查看自己 Kali 系统的 shadow 文件格式(注意需要 root 权限):

Bash
# 查看 root 用户的密码哈希格式(字段用 $ 分隔)
sudo grep "^root:" /etc/shadow
root:$y$j9T$QkL...(省略)...:20145:0:99999:7:::
# $y$ = yescrypt(Kali 默认) $6$ = SHA-512 $2b$ = bcrypt

Kali Linux 默认使用 yescrypt($y$)作为密码哈希算法,这也是一种内存硬哈希。如果在 /etc/shadow 中看到 $6$ 则是 SHA-512 crypt,看到 $1$ 就是古老的 MD5 crypt——后者意味着系统需要升级密码策略了。

04 编码 vs 加密:Base64 与 Hex

上一篇的小贴士里提到过"编码不是加密"。这个区别在安全领域至关重要——很多初学者看到 Base64 字符串就以为是"加密了",实际上任何人都能秒解。理解编码的本质,是避免安全误判的前提。

核心区别只有一句话:加密需要密钥才能还原,编码不需要任何密钥。编码只是换了一种数据表示方式,目的是"让数据能在特定环境中传输或存储",而不是"隐藏数据内容"。就像把中文翻译成英文——懂英文的人都能读,不存在"密钥"概念。

特性 加密 哈希 编码
可逆性 可逆(需密钥) 不可逆 可逆(无需密钥)
安全性 提供机密性 提供完整性 无安全性
典型代表 AES、RSA SHA-256、bcrypt Base64、Hex、URL

在 Kali 中用命令行实际操作三种最常见的编码:

Bash
# Base64 编码与解码
echo -n "admin:password123" | base64
YWRtaW46cGFzc3dvcmQxMjM=
echo "YWRtaW46cGFzc3dvcmQxMjM=" | base64 -d
admin:password123
# Hex 编码(十六进制)
echo -n "AB" | xxd -p
4142
# URL 编码(空格变 %20)
python3 -c "import urllib.parse; print(urllib.parse.quote('hello world&foo=bar'))"
hello%20world%26foo%3Dbar

上面的 Base64 例子正是 HTTP Basic Authentication 的实际格式——浏览器发送 Authorization: Basic YWRtaW46cGFzc3dvcmQxMjM= 时,服务端只需 base64 -d 就能还原出明文密码。这就是为什么 Basic Auth 必须配合 HTTPS 使用——否则凭据等于明文传输。

💡 小贴士
渗透测试中遇到 Base64 字符串的快速识别技巧:以 = 或 == 结尾、字符集为 A-Z/a-z/0-9/+//,长度是 4 的倍数。Kali 自带的 CyberChef 工具可自动识别多种编码格式。

05 哈希实战:完整性校验与密码破解

理解了哈希算法和编码的区别,现在进入实战环节。哈希在安全领域有两个高频应用场景:一是验证文件完整性(确认下载的文件没被篡改),二是密码破解(从哈希值反推密码明文)。前者是防御手段,后者是攻击手段——渗透测试中两者都会用到。

文件完整性校验

下载 Kali ISO 镜像时,官方网站会提供 SHA-256 校验值。下载完成后用 sha256sum 对比,确保文件在传输过程中没有被篡改或损坏:

Bash
# 计算文件的 SHA-256 哈希
sha256sum kali-linux-2026.1-installer-amd64.iso
a1b2c3d4e5f6...(64个字符)... kali-linux-2026.1-installer-amd64.iso
# 把哈希值保存到文件,方便后续自动校验
sha256sum kali-linux-2026.1-installer-amd64.iso > check.txt
# 校验:文件未被篡改时输出 OK
sha256sum -c check.txt
kali-linux-2026.1-installer-amd64.iso: OK

hashcat 密码破解

hashcat 是 Kali 自带的高性能密码破解工具,支持 CPU/GPU 加速。它用 -m 指定哈希类型,-a 指定攻击模式。常见的哈希类型编号:

-m 编号 哈希类型 GPU 破解速度(参考)
0 MD5 ~100 GH/s
100 SHA-1 ~70 GH/s
1400 SHA-256 ~30 GH/s
3200 bcrypt (cost=5) ~50 KH/s

上表用 RTX 4090 的参考速度展示了为什么 MD5 不能存密码——GPU 每秒可算 1000 亿次 MD5,8 位纯数字密码(1 亿种组合)不到 1 毫秒就穷举完了。而 bcrypt 即使在 cost=5(远低于推荐的 12)时,速度也骤降至每秒 5 万次——慢了 200 万倍。这就是密码哈希算法存在的意义。

下面用 hashcat 执行一次字典攻击。Kali 自带 /usr/share/wordlists/rockyou.txt 字典文件(需先解压):

Bash
# 解压 Kali 自带的 rockyou 字典
sudo gunzip /usr/share/wordlists/rockyou.txt.gz
# 把目标 MD5 哈希写入文件
echo "5f4dcc3b5aa765d61d8327deb882cf99" > target.hash
# 字典攻击:-m 0 (MD5) -a 0 (字典模式)
hashcat -m 0 -a 0 target.hash /usr/share/wordlists/rockyou.txt
5f4dcc3b5aa765d61d8327deb882cf99:password
# Session..........: hashcat
# Status...........: Cracked

哈希值 5f4dcc3b5aa765d61d8327deb882cf99 正是 "password" 的 MD5 值。hashcat 在 rockyou 字典(约 1400 万条密码)中瞬间找到了它。这就是为什么密码不能是常见词——字典攻击对弱密码几乎是秒破。

⚠️ 常见错误
hashcat -m 0 hash.txt wordlist.txt --force — --force 会跳过 GPU 检测警告,在无 GPU 的树莓派上可能导致结果不可靠
✓ 正确:树莓派上用 john --format=raw-md5 --wordlist=rockyou.txt target.hash 替代,CPU 模式更稳定

06 HMAC 与盐值:消息认证与彩虹表防御

前五章你学会了计算哈希、区分编码、破解弱密码哈希。但还剩两个问题没解决:第一,攻击者可以预计算一张"密码→哈希"对照表(彩虹表),拿到哈希值后直接查表——比逐个暴力破解快几个数量级。第二,普通哈希只能验证"数据没变",但无法验证"数据是谁发的"——任何人都能算 SHA-256,攻击者可以篡改数据后重新计算哈希替换掉。

盐值:让彩虹表失效

彩虹表的原理是"空间换时间"——预先计算好几亿个常见密码的哈希值存成巨型对照表。对抗方法很简单:在密码后面拼接一段随机字符串(称为盐,salt)再哈希。攻击者的彩虹表只针对无盐哈希,一旦加了随机盐,预计算的表就全废了——必须为每个盐值重新算一张表,得不偿失。

Python
# 无盐哈希:所有相同密码的哈希值相同
import hashlib
print(hashlib.sha256(b"password").hexdigest())
5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
# 加盐哈希:每个用户用不同盐值,哈希结果不同
import os, hashlib
salt = os.urandom(16)
hashed = hashlib.pbkdf2_hmac("sha256", b"password", salt, 600000)
# PBKDF2: 60万次迭代 + 随机盐,GPU 也头痛

bcrypt 和 Argon2id 自带盐值处理——你不需要手动管理盐。但如果用 PBKDF2 或 SHA-256,必须自己生成随机盐并和哈希值一起存储。盐值不需要保密,只需要保证每个用户不同且足够随机(至少 16 字节)。

HMAC:带密钥的哈希

HMAC(Hash-based Message Authentication Code)解决了"谁发的"问题。它在哈希计算中混入一个密钥——只有持有密钥的人才能生成正确的 HMAC。接收方用同样的密钥重新计算 HMAC,如果匹配则证明消息确实来自持有密钥的人且未被篡改。

消息 + 密钥
→
HMAC-SHA256
→
认证标签

HMAC 最常见的应用是 JWT(JSON Web Token)签名和 API 请求签名。下面用 Python 演示 HMAC 的生成与验证:

Python
import hmac, hashlib
secret_key = b"my-secret-key-2026"
message = b"transfer:1000:to_account_12345"
# 发送方:用密钥生成签名
signature = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
print(f"签名: {signature}")
签名: a3f5e2b1c4d8...(64个字符)
# 接收方:用相同密钥验证
expected = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
print(hmac.compare_digest(signature, expected))
True
💡 小贴士
验证 HMAC 时务必使用 hmac.compare_digest() 而非 ==。普通 == 在第一个不匹配字符处就返回 False,攻击者可通过测量响应时间逐字节猜出正确签名(时序攻击)。compare_digest 始终比较完整字符串,耗时恒定。

回顾本篇三个核心知识点:哈希算法提供完整性验证(数据没被篡改),盐值提供彩虹表防御(每个用户哈希唯一),HMAC 提供消息认证(确认发送方身份)。这三者和上一篇学的加密算法组合在一起,就构成了数字证书和 TLS 握手的基础——下一篇将把这些零件拼装成完整的 PKI 信任体系。

📖 知识回顾
单向散列函数 MD5 碰撞攻击 SHA-256 bcrypt cost 因子 Argon2id 内存硬 Base64 编码 编码 ≠ 加密 盐值防彩虹表 HMAC 消息认证 hashcat 字典攻击 文件完整性校验

动手练习

🟢 基础验证(5分钟)
在 Kali 终端创建一个文件 test.txt,写入任意内容,用 sha256sum test.txt > hash.txt 保存哈希。然后修改文件内容(加一个字),再执行 sha256sum -c hash.txt,观察输出变化。
参考解法:修改后 sha256sum -c hash.txt 会输出 test.txt: FAILED,并提示 sha256sum: WARNING: 1 computed checksum did NOT match。即使只改了一个字,哈希值也完全不同。
🟡 组合应用(10分钟)
字符串 YWRtaW46MTIzNDU2 是一段 Base64 编码。请用 Kali 命令行解码它,然后对解码后的字符串计算 MD5 哈希,最后用 hashcat 和 rockyou 字典尝试破解这个 MD5。
参考方向:先 echo "YWRtaW46MTIzNDU2" | base64 -d 解码得到 admin:123456。对 "123456" 计算 MD5 得到 e10adc3949ba59abbe56e057f20f883e,写入文件后 hashcat -m 0 -a 0 hash.txt /usr/share/wordlists/rockyou.txt 秒破。
🔴 开放挑战(30分钟+)
用 Python 实现一个安全的密码存储与验证模块:用户注册时生成随机盐,用 PBKDF2-HMAC-SHA256(迭代 600000 次)计算密码哈希,把盐和哈希一起存入字典。用户登录时取出盐重新计算并验证。额外挑战:实现一个"密码强度检测"函数,检查密码是否在 rockyou 字典中存在——如果在,拒绝注册并提示更换。
提示:使用 hashlib.pbkdf2_hmac("sha256", password.encode(), salt, 600000) 计算哈希。密码强度检测可以把 rockyou.txt 读入集合,用 password in rockyou_set 判断。注意 rockyou.txt 有约 1400 万行,加载到内存约 300MB。
下一篇:数字证书与 PKI
本篇你掌握了哈希算法(完整性)、盐值(彩虹表防御)和 HMAC(消息认证)。下一篇把加密(AES/RSA)+ 哈希(SHA-256)+ HMAC 组装成完整的 PKI 信任体系:X.509 证书结构、CA 信任链如何防止中间人攻击、TLS 握手如何用非对称加密协商对称密钥并用证书验证身份。这些知识将彻底打通 HTTPS 的底层原理。
关注 · 点赞 · 在看 持续获取系列更新