理解密码存储、哈希破解与密码策略加固
密码是身份认证的基石。据统计,81%的数据泄露与弱密码或被盗密码有关。理解密码如何存储、如何被破解,才能有效防御。
系统不存储明文密码,而是存储密码的哈希值。不同的哈希算法安全性差异巨大。
| 算法 | hashcat模式 | 安全性 | 说明 |
|---|---|---|---|
| MD5 | 0 | 🔴 已破解 | 速度太快,彩虹表可破 |
| SHA-1 | 100 | 🔴 已破解 | 碰撞攻击可行 |
| SHA-256 | 1400 | 🟡 不推荐 | 速度仍快,无salt易破 |
| SHA-512 | 1700 | 🟡 不推荐 | 比SHA-256稍好但不够 |
| bcrypt | 3200 | 🟢 推荐 | 自带salt,可调成本因子 |
| scrypt | 8900 | 🟢 推荐 | 内存密集,抗GPU/ASIC |
| Argon2 | — | 🟢 最推荐 | 2015年密码哈希竞赛冠军 |
# 使用openssl生成各种Hash
echo -n "password123" | openssl dgst -md5
# MD5(stdin)= 482c811da5d5b4bc6d497ffa98491e38
echo -n "password123" | openssl dgst -sha256
# SHA2-256(stdin)= ef92b778...e94f
echo -n "password123" | openssl dgst -sha512
# SHA2-512(stdin)= a3d2...
# 使用openssl生成密码Hash(推荐方式)
# SHA-512 with salt
openssl passwd -6 -salt mysalt
# 输入密码后生成: $6$mysalt$hash...
# bcrypt
openssl passwd -bcrypt -salt 22charactersalt
# 输入密码后生成: $2b$05$22charactersalt$hash...
# Linux /etc/shadow格式
# $id$salt$hash
# $1$ = MD5-crypt
# $5$ = SHA-256-crypt
# $6$ = SHA-512-crypt
# $y$ = yescrypt (现代Linux默认)
# $2b$ = bcrypt
# 通过Hash前缀识别算法
$1$... → MD5-crypt
$5$... → SHA-256-crypt
$6$... → SHA-512-crypt
$2a$/$2b$... → bcrypt
$y$... → yescrypt
$sha1$... → SHA-1-crypt
{SSHA}... → Netscape SSHA
$NT$... → NTLM
# 通过Hash长度识别
32字符 → MD5
40字符 → SHA-1
56字符 → SHA-224
64字符 → SHA-256
96字符 → SHA-384
128字符 → SHA-512
# 使用hash-identifier工具
pip install hashid
hashid "482c811da5d5b4bc6d497ffa98491e38"
# Analyzing '482c811da5d5b4bc6d497ffa98491e38'
# [+] MD5
# [+] MD4
# 安装hashcat
apt install hashcat
# 基本用法
# -m: 哈希类型 -a: 攻击模式
# 攻击模式: 0=字典, 1=组合, 3=掩码(暴力), 6=混合
# MD5字典破解
hashcat -m 0 -a 0 hashes.txt rockyou.txt
# SHA-256字典破解
hashcat -m 1400 -a 0 hashes.txt rockyou.txt
# bcrypt字典破解(很慢,bcrypt的设计目的)
hashcat -m 3200 -a 0 hashes.txt rockyou.txt
# 掩码暴力破解
# ?l=小写 ?u=大写 ?d=数字 ?s=特殊 ?a=所有
hashcat -m 0 -a 3 hashes.txt "?l?l?l?l?l?l?d?d"
# 6位小写字母+2位数字
# 常见密码模式掩码
hashcat -m 0 -a 3 hashes.txt "?u?l?l?l?l?d?d?d?s"
# 大写开头+4位小写+3位数字+特殊字符 (如: Admin123!)
# 基于规则的变换
hashcat -m 0 -a 0 hashes.txt rockyou.txt -r best64.rule
# best64.rule包含64种常见变换:首字母大写、加数字、加符号等
# 组合攻击
hashcat -m 0 -a 1 hashes.txt wordlist1.txt wordlist2.txt
# 查看已破解结果
hashcat -m 0 hashes.txt --show
# 常用字典
# /usr/share/wordlists/rockyou.txt (Kali内置)
# https://github.com/danielmiessler/SecLists
# 安装John
apt install john
# 基本字典破解
john --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt
# 指定格式
john --format=raw-md5 --wordlist=rockyou.txt hashes.txt
john --format=raw-sha256 --wordlist=rockyou.txt hashes.txt
john --format=bcrypt --wordlist=rockyou.txt hashes.txt
# Linux shadow文件破解
# 先用unshadow合并passwd和shadow
unshadow /etc/passwd /etc/shadow > unshadowed.txt
john --wordlist=rockyou.txt unshadowed.txt
# 查看破解结果
john --show unshadowed.txt
# 暴力破解(不推荐,太慢)
john --incremental hashes.txt
# 使用规则增强字典
john --wordlist=rockyou.txt --rules hashes.txt
# ZIP文件密码破解
zip2john protected.zip > zip_hash.txt
john zip_hash.txt
# RAR文件密码破解
rar2john protected.rar > rar_hash.txt
john rar_hash.txt
# 彩虹表是预计算的Hash→明文查找表
# 对于无salt的MD5/SHA-1极其有效
# 使用rcrack彩虹表破解
# 下载彩虹表: https://www.freerainbowtables.com/
rcrack *.rt -h 482c811da5d5b4bc6d497ffa98491e38
# 使用rtgen生成彩虹表
rtgen md5 loweralpha-numeric 1 7 0 3800 33554432 0
# 使用在线彩虹表查询
# https://crackstation.net/
# https://www.md5online.org/
# 防御彩虹表的关键: Salt!
# 相同密码 + 不同salt = 不同Hash
# Salt使预计算攻击不可行
echo -n "password" | openssl dgst -md5 # 无salt
# 5f4dcc3b5aa765d61d8327deb882cf99
echo -n "password" | openssl passwd -1 -salt abc
# $1$abc$BVZPCzXVvE2d7hH9CjFFs/
echo -n "password" | openssl passwd -1 -salt xyz
# $1$xyz$7BqnaGJQv7eXNdM6YPGaN/
# 相同密码,不同salt,完全不同的Hash
# Linux密码策略配置 /etc/login.defs
PASS_MAX_DAYS 90 # 密码最长有效期
PASS_MIN_DAYS 7 # 最短修改间隔
PASS_MIN_LEN 12 # 最短长度
PASS_WARN_AGE 14 # 过期前警告天数
# 使用pam_pwquality设置复杂度要求
# /etc/security/pwquality.conf
minlen = 12 # 最短12位
minclass = 3 # 至少包含3类字符
dcredit = -1 # 至少1个数字
ucredit = -1 # 至少1个大写
lcredit = -1 # 至少1个小写
ocredit = -1 # 至少1个特殊字符
maxrepeat = 3 # 最多3个连续相同字符
maxsequence = 3 # 最多3个连续字符(abc, 123)
enforce_for_root # root也要遵守规则
# 永远不要存储明文密码!
# 使用Argon2id(最佳选择)
python3 -c "
import argon2
ph = argon2.PasswordHasher()
hash = ph.hash('mypassword')
print(hash)
# $argon2id$v=19$m=65536,t=3,p=4$salt$hash
"
# 使用bcrypt(次优选择)
python3 -c "
import bcrypt
hash = bcrypt.hashpw(b'mypassword', bcrypt.gensalt(rounds=12))
print(hash)
# $2b$12$22字符salt$31字符hash
"
# 禁止使用的算法
# ❌ MD5 — 太快,彩虹表可破
# ❌ SHA-1 — 碰撞攻击
# ❌ 简单SHA-256/512无salt — 速度快,易暴力
# ✅ bcrypt — 自带salt,可调成本
# ✅ Argon2id — 内存密集,抗GPU/ASIC
# 安装Google Authenticator PAM模块
apt install libpam-google-authenticator
# 为用户配置TOTP
google-authenticator
# 配置SSH使用MFA /etc/pam.d/sshd
auth required pam_google_authenticator.so
# /etc/ssh/sshd_config
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
# 重启SSH
systemctl restart sshd
# 使用Have I Been Pwned API检查密码是否泄露
# https://haveibeenpwned.com/API/v3#PwnedPasswords
# K-匿名模型:只发送Hash前5位
# 手动查询
HASH=$(echo -n "password123" | openssl dgst -sha1 | awk '{print $NF}')
PREFIX=${HASH:0:5}
SUFFIX=${HASH:5}
curl -s "https://api.pwnedpasswords.com/range/$PREFIX" | \
grep -i "$SUFFIX"
# 返回该密码在泄露数据库中出现的次数
# 使用pwned-passwords-django集成到应用
pip install pwned-passwords-django
# 在注册时检查
from pwned_passwords_django.api import pwned_password
if pwned_password(request.POST['password']):
# 密码已泄露,拒绝使用