部分内容来源:JavaGuide
哈希算法也叫散列函数或摘要算法,它的作用是对任意长度的数据生成一个固定长度的唯一标识
也叫哈希值、散列值或消息摘要
哈希算法的是不可逆的,你无法通过哈希之后的值再得到原值
哈希值的作用是可以用来验证数据的完整性和一致性
哈希算法可以简单分为两类:
除了这两种之外,还有一些特殊的哈希算法,例如安全性更高的慢哈希算法
哈希算法一般是不需要密钥的,但也存在部分特殊哈希算法需要密钥。例如,MAC 和 SipHash 就是一种基于密钥的哈希算法,它在哈希算法的基础上增加了一个密钥,使得只有知道密钥的人才能验证数据的完整性和来源
彩虹表 是一种预先计算好的 哈希值与明文密码的映射表,用于快速破解哈希算法加密的密码
它通过 时间-存储权衡大幅减少暴力破解所需的时间
尤其对弱密码或未加盐(Unsalted)的哈希攻击效果极强
方法 |
过程 |
缺点 |
暴力破解 |
逐个尝试所有可能的密码组合(如 a→b→...→zzzz ),计算哈希并比对 |
速度极慢,需实时计算所有可能性 |
彩虹表 |
提前计算并存储大量「明文→哈希」的映射,破解时直接查表 |
占用存储空间,但破解速度极快 |
减少存储量:不直接存储所有明文-哈希对,而是通过 哈希链(Hash Chain) 压缩数据
例如:明文1 → 哈希1 → 明文2 → 哈希2 → ... → 明文N → 哈希N,只存储链的起点和终点
碰撞还原:通过链式推导,从哈希值反向还原明文(需多次计算)
目标密码的哈希值 未加盐(No Salt)
密码本身 强度低(如常见单词、短密码)
MD5、SHA-1 等快速哈希算法(计算速度快,易生成彩虹表)
数据库泄露的密码哈希(如 password_hash = md5("123456"))
早年 LinkedIn 密码泄露事件(600万密码用 SHA-1 未加盐存储,被彩虹表秒破)
Wi-Fi WPA2 破解:用彩虹表加速 PSK(预共享密钥)破解
在密码哈希前,拼接一个 随机盐值(每个用户不同):
salt = random_bytes(16) # 随机生成16字节盐
hash = sha256(salt + password)
效果:即使相同密码,哈希结果也不同,使彩虹表失效
选择 bcrypt、Argon2、PBKDF2 等故意降低计算速度的算法。
例如:bcrypt.hashpw(password, bcrypt.gensalt())
通过多次迭代(如 10万次哈希)增加计算成本:
pythonhash = pbkdf2_hmac('sha256', password, salt, 100000)
强制用户使用 长密码+特殊字符(如 MyP@ssw0rd!2023)
禁用常见弱密码(如 123456、password)
攻击方式 |
特点 |
防御措施 |
彩虹表 |
依赖预计算表,对未加盐哈希高效 |
加盐 + 慢哈希 |
暴力破解 |
逐个尝试所有组合,速度慢 |
增加密码复杂度 + 限速尝试 |
字典攻击 |
尝试常见单词和变种 |
禁用常见密码 |
GPU/ASIC 破解 |
硬件加速暴力破解 |
使用抗硬件算法(如 Argon2) |
逐渐失效:现代系统普遍采用 加盐+慢哈希,彩虹表难以直接使用
仍存威胁:对老旧系统或错误配置(如未加盐的 MD5)依然有效
✅ 最佳实践:
永远使用 加盐的 bcrypt/Argon2 存储密码
定期审计系统中的哈希算法(禁用 MD5/SHA-1)
加密哈希算法主要用于 安全敏感场景,如密码存储、数字签名、数据完整性校验等
它们必须满足严格的密码学特性
极难找到两个不同的输入 x ≠ y,使得 Hash(x) = Hash(y)
例如:SHA-256、SHA-3、BLAKE3
给定哈希值 h,难以反推出原始输入 x(即 Hash(x) = h)
例如:MD5(已不安全)、SHA-1(已不安全)、SHA-256
算法 |
输出长度(bit) |
安全性状态 |
典型用途 |
MD5 |
128 |
❌ 已破解(碰撞攻击) |
文件校验(非安全场景) |
SHA-1 |
160 |
❌ 已破解(碰撞攻击) |
旧版 TLS/SSL(已弃用) |
SHA-256 |
256 |
✅ 安全 |
比特币、密码存储 |
SHA-3 |
可变(224/256/384/512) |
✅ 安全 |
替代 SHA-2 |
BLAKE3 |
可变(默认 256) |
✅ 安全 |
高性能哈希 |
非加密哈希算法 不追求密码学安全性,而是专注于 高性能 和 低碰撞率
适用于哈希表、缓存、数据分片等场景
算法 |
特点 |
典型用途 |
MurmurHash |
高性能,适合短文本 |
Redis、HashMap |
xxHash |
极快,适合大文件 |
数据分片、校验 |
CityHash |
Google 优化,适合字符串 |
大数据处理 |
FNV-1a |
简单,低碰撞 |
数据库索引、缓存键 |
慢哈希算法是专门设计用于 密码存储 的安全哈希算法
其核心特点是 故意降低计算速度,以抵御暴力破解和彩虹表攻击
这类算法通常结合加盐和多次迭代,大幅增加攻击者的破解成本
让合法用户验证密码时稍微慢一点,但让攻击者破解密码的成本高到无法承受
普通哈希算法(如 SHA-256、MD5)虽然安全,但 计算速度过快,攻击者可通过以下方式快速破解:
慢哈希通过 人为引入计算延迟(如 100ms/次),使得攻击者需要数年甚至更长时间才能破解一个密码
原理:基于 HMAC(如 HMAC-SHA256)的多次迭代(通常 10万+ 次)
特点:
import hashlib, binascii, os
salt = os.urandom(16) # 随机盐
password = "user_password".encode()
hash = hashlib.pbkdf2_hmac('sha256', password, salt, 100000)
print(binascii.hexlify(hash).decode())
原理:基于 Blowfish 加密算法,引入 工作因子(Work Factor) 控制计算成本
特点:
import bcrypt
password = "user_password".encode()
salt = bcrypt.gensalt(rounds=12) # 工作因子=12(2^12 次迭代)
hash = bcrypt.hashpw(password, salt)
print(hash.decode())
原理:同时消耗 CPU 和内存资源,抗 GPU/ASIC 破解
变种:
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3, # 迭代次数
memory_cost=65536, # 内存消耗(KB)
parallelism=4 # 并行线程数
)
hash = ph.hash("user_password")
print(hash)
特性 |
慢哈希(如 bcrypt) |
普通哈希(如 SHA-256) |
计算速度 |
故意慢(100ms~1s/次) |
极快(微秒级) |
抗暴力破解 |
✅ 大幅增加攻击成本 |
❌ 易被暴力破解 |
抗彩虹表 |
✅ 必须加盐 |
❌ 需额外加盐 |
适用场景 |
密码存储 |
数据完整性校验、数字签名 |
Argon2 > bcrypt > PBKDF2(Argon2 是当前最安全的方案)
PBKDF2:迭代次数 ≥ 100,000 次
bcrypt:工作因子 ≥ 12
Argon2:time_cost≥3,memory_cost≥64MB,parallelism≥4
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
password_hash VARCHAR(100), -- 存储 bcrypt/Argon2 哈希值
salt VARCHAR(50) -- PBKDF2 需单独存盐
);
# 注册时哈希密码
def register(username, password):
salt = generate_salt()
hash = argon2.hash(password + salt)
db.save(username, hash, salt)
# 登录时验证
def login(username, password):
user = db.get_user(username)
if argon2.verify(password + user.salt, user.hash):
return "Login success!"
加盐 Hash 是一种密码安全存储技术,通过在原始密码 拼接随机数据(Salt,盐值) 后再进行哈希计算,使得即使相同的密码也会生成不同的哈希值,从而有效防御 彩虹表攻击 和 批量破解
如果直接存储 Hash(password),攻击者可以通过以下方式破解:
示例(不安全存储):
用户名 |
密码(MD5) |
Alice |
5f4dcc3b5aa765d61d8327deb882cf99 (password) |
Bob |
5f4dcc3b5aa765d61d8327deb882cf99 (password) |
攻击者发现 Alice 和 Bob 的哈希相同,直接破解一次即可获取两人密码
加盐后的存储示例:
用户名 |
盐值(Salt) |
密码哈希(Salt + Password) |
Alice |
a1b2c3... (随机) |
sha256("a1b2c3...password") → x1y2z3... |
Bob |
d4e5f6... (随机) |
sha256("d4e5f6...password") → p7q8r9... |
即使密码相同,哈希值也不同,攻击者必须逐个破解!
生成随机盐值(每个用户唯一):
import os
salt = os.urandom(16) # 生成 16 字节随机盐
计算加盐哈希:
import hashlib
password = "user_password".encode()
hash = hashlib.sha256(salt + password).hexdigest() # 加盐哈希
存储盐值和哈希:
INSERT INTO users (username, salt, password_hash)
VALUES ('alice', 'a1b2c3...', 'x1y2z3...');
从数据库取出盐值和哈希:
sqlSELECT salt, password_hash FROM users WHERE username = 'alice';
验证密码:
input_password = "user_input".encode()
computed_hash = hashlib.sha256(salt + input_password).hexdigest()
if computed_hash == stored_hash:
print("登录成功!")
攻击方式 |
无盐哈希的风险 |
加盐哈希的防御效果 |
彩虹表攻击 |
直接查表破解 |
盐值使预计算表失效,必须重新生成 |
批量破解 |
破解一个哈希等于破解所有相同密码 |
每个用户的哈希不同,必须逐个破解 |
字典攻击 |
快速尝试常见密码 |
盐值增加密码复杂性,降低破解效率 |
即使使用了唯一且随机的盐值,如果哈希算法本身速度过快(如 SHA-256、MD5),攻击者仍可以:
技术 |
作用 |
加盐的必要性 |
加盐(Salt) |
防止彩虹表攻击和批量破解 |
✅ 必须 |
慢哈希 |
增加暴力破解成本 |
✅ 建议配合使用 |
密钥拉伸 |
通过多次迭代增加计算时间 |
✅ 建议配合使用 |
import os
import hashlib
def hash_password(password):
salt = os.urandom(16) # 生成随机盐
hash = hashlib.sha256(salt + password.encode()).hexdigest()
return salt.hex(), hash # 返回盐和哈希
salt, hash = hash_password("my_secure_password")
print(f"Salt: {salt}, Hash: {hash}")
bcrypt可以从hash后字符串自动提取盐值,这样子用户下一次登陆时候的原始密码就能结合原始盐值进行hash运算得到原来的hash值
import bcrypt
password = "my_secure_password".encode()
hash = bcrypt.hashpw(password, bcrypt.gensalt()) # 自动加盐+慢哈希
print(f"Hash: {hash.decode()}")
MD 算法有多个版本,包括 MD2、MD4、MD5 等,其中 MD5 是最常用的版本,它可以生成一个 128 位(16 字节)的哈希值
从安全性上说:MD5 > MD4 > MD2。除了这些版本,还有一些基于 MD4 或 MD5 改进的算法,如 RIPEMD、HAVAL 等
即使是最安全 MD 算法 MD5 也存在被破解的风险,攻击者可以通过暴力破解或彩虹表攻击等方式,找到与原始数据相同的哈希值,从而破解数据
为了增加破解难度,通常可以选择加盐
盐(Salt)在密码学中,是指通过在密码任意固定位置插入特定的字符串,让哈希后的结果和使用原始密码的哈希结果不相符,这种过程称之为“加盐”
加盐之后就安全了吗?并不一定,这只是增加了破解难度,不代表无法破解。而且,MD5 算法本身就存在弱碰撞(Collision)问题,即多个不同的输入产生相同的 MD5 值
因此,MD 算法已经不被推荐使用,建议使用更安全的哈希算法比如 SHA-2、Bcrypt。
Java 提供了对 MD 算法系列的支持,包括 MD2、MD5
MD5 代码示例(未加盐):
String originalString = "KIRA";
// 创建MD5摘要对象
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(originalString.getBytes(StandardCharsets.UTF_8));
// 计算哈希值
byte[] result = messageDigest.digest();
// 将哈希值转换为十六进制字符串
String hexString = new HexBinaryAdapter().marshal(result);
System.out.println("Original String: " + originalString);
System.out.println("MD5 Hash: " + hexString.toLowerCase());
输出:
Original String: KIRA
MD5 Hash: fb246796f5b1b60d4d0268c817c608fa
SHA(Secure Hash Algorithm)系列算法是一组密码哈希算法
将任意长度的数据映射为固定长度的哈希值
SHA 系列算法由美国国家安全局(NSA)于 1993 年设计,目前共有 SHA-1、SHA-2、SHA-3 三种版本。
SHA-1 算法将任意长度的数据映射为 160 位的哈希值。然而,SHA-1 算法存在一些严重的缺陷,比如安全性低,容易受到碰撞攻击和长度扩展攻击。
因此,SHA-1 算法已经不再被推荐使用。
SHA-2 家族(如 SHA-256、SHA-384、SHA-512 等)和 SHA-3 系列是 SHA-1 算法的替代方案,它们都提供了更高的安全性和更长的哈希值长度。
SHA-2 家族是在 SHA-1 算法的基础上改进而来的,它们采用了更复杂的运算过程和更多的轮次,使得攻击者更难以通过预计算或巧合找到碰撞
为了寻找一种更安全和更先进的密码哈希算法,美国国家标准与技术研究院(National Institute of Standards and Technology,简称 NIST)在 2007 年公开征集 SHA-3 的候选算法。NIST 一共收到了 64 个算法方案,经过多轮的评估和筛选,最终在 2012 年宣布 Keccak 算法胜出,成为 SHA-3 的标准算法(SHA-3 与 SHA-2 算法没有直接的关系)。 Keccak 算法具有与 MD 和 SHA-1/2 完全不同的设计思路,即海绵结构(Sponge Construction),使得传统攻击方法无法直接应用于 SHA-3 的攻击中(能够抵抗目前已知的所有攻击方式包括碰撞攻击、长度扩展攻击、差分攻击等)
由于 SHA-2 算法还没有出现重大的安全漏洞,而且在软件中的效率更高,所以大多数人还是倾向于使用 SHA-2 算法。
相比 MD5 算法,SHA-2 算法之所以更强,主要有两个原因:
当然,SHA-2 也不是绝对安全的,也有被暴力破解或者彩虹表攻击的风险,所以,在实际的应用中,加盐还是必不可少的
Java 提供了对 SHA 算法系列的支持,包括 SHA-1、SHA-256、SHA-384 和 SHA-512
SHA-256 代码示例(未加盐):
String originalString = "Java学习 + 面试指南:javaguide.cn";
// 创建SHA-256摘要对象
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(originalString.getBytes());
// 计算哈希值
byte[] result = messageDigest.digest();
// 将哈希值转换为十六进制字符串
String hexString = new HexBinaryAdapter().marshal(result);
System.out.println("Original String: " + originalString);
System.out.println("SHA-256 Hash: " + hexString.toLowerCase());
输出
Original String: Java学习 + 面试指南:javaguide.cn
SHA-256 Hash: 184eb7e1d7fb002444098c9bde3403c6f6722c93ecfac242c0e35cd9ed3b41cd
bcrypt 是慢哈希算法,且在很长一段时间内是密码存储的优选方案
但随着硬件技术的发展,其 “仅依赖计算耗时” 的设计逐渐显现短板
而 Argon2 等结合了计算成本和内存成本的算法,在抗现代攻击方面更具优势
不过,对于安全性要求不极致且需兼顾兼容性的场景,bcrypt 仍是可靠选择
Bcrypt算法是一种基于 Blowfish 加密算法的密码哈希算法,专门为密码加密而设计,安全性高
Bcrypt算法可以自动加盐
bcrypt可以从hash后字符串自动提取盐值,这样子用户下一次登陆时候的原始密码就能结合原始盐值进行hash运算得到原来的hash值
由于 Bcrypt 采用了 salt(盐) 和 cost(成本) 两种机制,它可以有效地防止彩虹表攻击和暴力破解攻击,从而保证密码的安全性
salt: 是一个随机生成的字符串,用于和密码混合,增加密码的复杂度和唯一性
cost :是一个数值参数,用于控制 Bcrypt 算法的迭代次数,增加密码哈希的计算时间和资源消耗
Bcrypt 算法可以根据实际情况进行调整加密的复杂度,可以设置不同的 cost 值和 salt 值,从而满足不同的安全需求,灵活性很高
Java 应用程序的安全框架 Spring Security 支持多种密码编码器
其中BCryptPasswordEncoder是官方推荐的一种,它使用 BCrypt 算法对用户的密码进行加密存储
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
Argon2 是目前密码哈希领域的佼佼者
凭借其抗暴力破解能力和灵活的参数配置,成为许多安全场景的首选
它在 2015 年密码哈希竞赛中夺冠,如今已被广泛应用于各类系统中
核心优势在于能有效抵御GPU 加速攻击 和 内存 - hard 攻击
在密码存储中,直接存储明文密码风险极大,而普通哈希算法(如 MD5、SHA-256)因计算速度快,容易被暴力破解工具(如彩虹表)攻破
Argon2 则通过故意设计的高计算成本和内存消耗,大幅提高破解难度,同时支持并行计算优化,平衡安全性与性能
Argon2 有三个变种,适用于不同场景,核心差异在于是否利用数据依赖和内存访问模式:
类型 |
特点 |
适用场景 |
Argon2d |
数据依赖的内存访问,抗 GPU 攻击能力最强,但可能存在侧信道攻击风险 |
对侧信道攻击不敏感的场景(如离线密码哈希) |
Argon2i |
数据独立的内存访问,通过密码哈希保护内存访问模式,侧信道安全性更高 |
密码验证(如登录系统) |
Argon2id |
结合前两者优势,先使用 Argon2i 再用 Argon2d,平衡安全性和抗攻击能力 |
推荐的通用场景(如大多数应用程序) |
注意:Argon2id 是目前最推荐的类型,在 RFC 9106 中被指定为标准,兼顾安全性和实用性
普通应用的威胁是 “两头都要防”:
而 Argon2id 是 “混合体”:先按 Argon2i 的方式防侧信道攻击,再按 Argon2d 的方式防 GPU 破解,等于 “兼顾了两种防护”。普通应用里,这两种威胁都可能存在,所以选它最稳妥
离线场景(比如本地存储的密码文件,不联网验证)的核心威胁是 “被黑客拿到文件后,用 GPU 暴力破解”。
Argon2d 抗 GPU 破解的能力最强,但它的内存访问路径和密码相关,容易被侧信道攻击抓漏洞
但 “离线场景” 通常是在你自己的设备上(比如加密的本地密码管理器),别人很难物理接触设备搞侧信道攻击(总不能撬开你电脑测电流吧),所以可以放心用它的 “强项”
有些场景特别怕 “侧信道攻击”,比如加密芯片(银行 U 盾、智能卡)—— 这些设备体积小,容易被物理接触,黑客可能通过监测它工作时的耗电、发热变化来反推密码
Argon2i 的内存访问路径和密码无关,就算被监测,也泄露不了密码相关的规律,所以侧信道安全性最高。虽然它抗 GPU 破解的能力弱一点,但这类场景里,“防物理监测” 比 “防暴力破解” 更重要
日常开发不用纠结,直接用 Argon2id,这是官方认证的 “万能款”
Argon2 的安全性通过以下参数控制,需根据系统性能和安全需求调整:
示例配置(适用于多数应用):t=3, m=65536, p=4,即 3 轮迭代、64MB 内存、4 线程并行
大多数编程语言都有 Argon2 的实现库,以下是常见场景的使用示例:
密码加密(Java 示例,使用 argon2-jvm 库):
Argon2 argon2 = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id);
String salt = new SecureRandom().generateSeed(16); // 生成16字节盐值
String hash = argon2.hash(3, 65536, 4, salt, "user_password".getBytes());
密码验证:
存储哈希值时需包含盐值和参数(格式:$argon2id$v=19$m=65536,t=3,p=4$salt$hash)
验证时直接使用库函数对比输入密码与存储的哈希值
哈希算法的是不可逆的,你无法通过哈希之后的值再得到原值
Hash算法分类:
彩虹表 是一种预先计算好的 哈希值与明文密码的映射表,用于快速破解哈希算法加密的密码
它通过 时间-存储权衡 大幅减少暴力破解所需的时间
尤其对弱密码或未加盐(Unsalted)的哈希攻击效果极强
可以通过加盐和慢Hash增大攻击成本,阻止彩虹表的暴力破解
MD5目前就已经被彩虹表暴力破解了,所以现在不推荐用MD5了,重要数据Hash场景更不该用,用的话要加盐
防止下面两个问题:
市面上的加密算法都有彩虹表算出对应的值,不加盐的话容易被猜出来
如果不加盐,破解出了一个密码,攻击者发现 Alice 和 Bob 的哈希相同,直接破解一次即可获取两人密码
又快又安全,碰撞概率极低
场景:比如我们日常下载软件安装包时,官方通常会提供该安装包的 SHA256 哈希值
这种场景要求性能快和不碰撞,这种hash值破解了也几乎没啥用
例如普通的模运算
特点:
场景:
核心特点:故意降低计算速度,以抵御暴力破解和彩虹表攻击
通常结合加盐和多次迭代大幅增加攻击者的破解成本
慢Hash的目的:
让合法用户验证密码时稍微慢一点,但让攻击者破解密码的成本高到无法承受
慢哈希通过 人为引入计算延迟,使得攻击者需要数年甚至更长时间才能破解一个密码
慢Hash算法:
底层都有自动加盐,目前推荐Argon2,它在 2015 年密码哈希竞赛中夺冠
bcypt只能从时间层面加大成本,但是Argon2能在时间+内存空间层面加大成本
推荐使用SHA-256
MD5已经被彩虹表破解
SHA-256适用于数据校验场景
bcrypt 是慢哈希算法,且在很长一段时间内是密码存储的优选方案
但随着硬件技术的发展,其 【仅依赖计算耗时】 的设计逐渐显现短板
而 Argon2 等结合了计算成本和内存成本的算法,在抗现代攻击方面更具优势
不过,对于安全性要求不极致且需兼顾兼容性的场景,bcrypt 仍是可靠选择
bcrypt和Argon2底层都自动加盐
Bcrypt 采用了 salt(盐) 和 cost(成本) 两种机制,它可以有效地防止彩虹表攻击和暴力破解攻击,从而保证密码的安全性
salt: 是一个随机生成的字符串,用于和密码混合,增加密码的复杂度和唯一性
cost :是一个数值参数,用于控制 Bcrypt 算法的迭代次数,增加密码哈希的计算时间和资源消耗
Argon2 则通过故意设计的高计算成本和内存消耗,大幅提高破解难度,同时支持并行计算优化,平衡安全性与性能
黑客可以通过暴力运算破解和侧信道攻击(通过物理设备查看电流和内存规律推导出密码)
三种Argon2
一般场景推荐使用Argon2id
Argon2 的安全性通过以下参数控制,需根据系统性能和安全需求调整:
示例配置:t=3, m=65536, p=4,即 3 轮迭代、64MB 内存、4 线程并行