部分内容来源:JavaGuide
什么是Hash算法
哈希算法也叫散列函数或摘要算法,它的作用是对任意长度的数据生成一个固定长度的唯一标识
也叫哈希值、散列值或消息摘要
哈希算法的是不可逆的,你无法通过哈希之后的值再得到原值
哈希值的作用是可以用来验证数据的完整性和一致性
哈希算法可以简单分为两类:
加密哈希算法:安全性较高的哈希算法,它可以提供一定的数据完整性保护和数据防篡改能力,能够抵御一定的攻击手段,安全性相对较高,但性能较差,适用于对安全性要求较高的场景。例如 SHA2、SHA3、SM3、RIPEMD-160、BLAKE2、SipHash 等等非加密哈希算法:安全性相对较低的哈希算法,易受到暴力破解、冲突攻击等攻击手段的影响,但性能较高,适用于对安全性没有要求的业务场景。例如 CRC32、MurMurHash3、SipHash 等等。、
除了这两种之外,还有一些特殊的哈希算法,例如安全性更高的慢哈希算法
哈希算法一般是不需要密钥的,但也存在部分特殊哈希算法需要密钥。例如,MAC 和 SipHash 就是一种基于密钥的哈希算法,它在哈希算法的基础上增加了一个密钥,使得只有知道密钥的人才能验证数据的完整性和来源
什么是彩虹表
彩虹表 是一种预先计算好的 哈希值与明文密码的映射表,用于快速破解哈希算法加密的密码
它通过 时间-存储权衡大幅减少暴力破解所需的时间
尤其对弱密码或未加盐(Unsalted)的哈希攻击效果极强
1. 彩虹表的工作原理
(1)普通暴力破解 vs 彩虹表
方法
过程
缺点
暴力破解
逐个尝试所有可能的密码组合(如 a→b→...→zzzz
),计算哈希并比对
速度极慢,需实时计算所有可能性
彩虹表
提前计算并存储大量「明文→哈希」的映射,破解时直接查表
占用存储空间,但破解速度极快
(2)彩虹表的优化技术
减少存储量:不直接存储所有明文-哈希对,而是通过 哈希链(Hash Chain) 压缩数据
例如:明文1 → 哈希1 → 明文2 → 哈希2 → ... → 明文N → 哈希N,只存储链的起点和终点
碰撞还原:通过链式推导,从哈希值反向还原明文(需多次计算)
2. 彩虹表的攻击场景
(1)成功条件
目标密码的哈希值 未加盐(No Salt)
密码本身 强度低(如常见单词、短密码)
(2)典型受害算法
MD5、SHA-1 等快速哈希算法(计算速度快,易生成彩虹表)
数据库泄露的密码哈希(如 password_hash = md5("123456"))
(3)实际案例
早年 LinkedIn 密码泄露事件(600万密码用 SHA-1 未加盐存储,被彩虹表秒破)
Wi-Fi WPA2 破解:用彩虹表加速 PSK(预共享密钥)破解
3. 如何防御彩虹表攻击?
(1)加盐(Salt)
在密码哈希前,拼接一个 随机盐值(每个用户不同):
salt = random_bytes(16) # 随机生成16字节盐
hash = sha256(salt + password)
效果:即使相同密码,哈希结果也不同,使彩虹表失效
(2)使用慢哈希算法
选择 bcrypt、Argon2、PBKDF2 等故意降低计算速度的算法。
例如:bcrypt.hashpw(password, bcrypt.gensalt())
(3)密钥拉伸(Key Stretching)
通过多次迭代(如 10万次哈希)增加计算成本:
pythonhash = pbkdf2_hmac('sha256', password, salt, 100000)
(4)密码策略
强制用户使用 长密码+特殊字符(如 MyP@ssw0rd!2023)
禁用常见弱密码(如 123456、password)
4. 彩虹表 vs 其他攻击方式
攻击方式
特点
防御措施
彩虹表
依赖预计算表,对未加盐哈希高效
加盐 + 慢哈希
暴力破解
逐个尝试所有组合,速度慢
增加密码复杂度 + 限速尝试
字典攻击
尝试常见单词和变种
禁用常见密码
GPU/ASIC 破解
硬件加速暴力破解
使用抗硬件算法(如 Argon2)
5. 彩虹表现状
逐渐失效:现代系统普遍采用 加盐+慢哈希,彩虹表难以直接使用
仍存威胁:对老旧系统或错误配置(如未加盐的 MD5)依然有效
✅ 最佳实践:
永远使用 加盐的 bcrypt/Argon2 存储密码
定期审计系统中的哈希算法(禁用 MD5/SHA-1)
加密Hash
加密哈希算法主要用于 安全敏感场景,如密码存储、数字签名、数据完整性校验等
它们必须满足严格的密码学特性
核心特性
抗碰撞性(Collision Resistance)
极难找到两个不同的输入 x ≠ y,使得 Hash(x) = Hash(y)
例如:SHA-256、SHA-3、BLAKE3
单向性(Pre-image Resistance)
给定哈希值 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)
✅ 安全
高性能哈希
典型应用
密码存储(加盐哈希,如 PBKDF2、bcrypt、Argon2)区块链(比特币使用 SHA-256)数字签名(如 RSA + SHA-256)文件完整性校验(如下载文件的 SHA-256 校验和)
非加密Hash
非加密哈希算法 不追求密码学安全性,而是专注于 高性能 和 低碰撞率
适用于哈希表、缓存、数据分片等场景
核心特点
计算速度快(比加密哈希快 10-100 倍)不保证抗碰撞性(可能被恶意构造碰撞)不保证单向性(可能被逆向工程)
常见非加密哈希算法
算法
特点
典型用途
MurmurHash
高性能,适合短文本
Redis、HashMap
xxHash
极快,适合大文件
数据分片、校验
CityHash
Google 优化,适合字符串
大数据处理
FNV-1a
简单,低碰撞
数据库索引、缓存键
典型应用
哈希表(如 Java 的 HashMap 使用 hashCode())分布式系统(如一致性哈希 Consistent Hashing)缓存键生成(如 Redis 的键哈希)数据分片(如数据库分库分表)
慢Hash算法
慢哈希算法是专门设计用于 密码存储 的安全哈希算法
其核心特点是 故意降低计算速度,以抵御暴力破解和彩虹表攻击
这类算法通常结合加盐和多次迭代,大幅增加攻击者的破解成本
慢Hash的设计目的
让合法用户验证密码时稍微慢一点,但让攻击者破解密码的成本高到无法承受
为什么需要慢哈希?
普通哈希算法(如 SHA-256、MD5)虽然安全,但 计算速度过快,攻击者可通过以下方式快速破解:
暴力破解:尝试数百万/秒的密码组合彩虹表攻击:预计算哈希值与明文密码的映射表
慢哈希通过 人为引入计算延迟(如 100ms/次),使得攻击者需要数年甚至更长时间才能破解一个密码
常见慢哈希算法
1. PBKDF2(Password-Based Key Derivation Function 2)
原理:基于 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())
2. bcrypt
原理:基于 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())
3. Argon2(2015 年密码哈希竞赛冠军)
原理:同时消耗 CPU 和内存资源,抗 GPU/ASIC 破解
变种:
Argon2i:抗侧信道攻击(适合密码存储)Argon2d:抗 GPU 破解(适合加密货币)
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3, # 迭代次数
memory_cost=65536, # 内存消耗(KB)
parallelism=4 # 并行线程数
)
hash = ph.hash("user_password")
print(hash)
慢哈希 vs 普通哈希
特性
慢哈希(如 bcrypt)
普通哈希(如 SHA-256)
计算速度
故意慢(100ms~1s/次)
极快(微秒级)
抗暴力破解
✅ 大幅增加攻击成本
❌ 易被暴力破解
抗彩虹表
✅ 必须加盐
❌ 需额外加盐
适用场景
密码存储
数据完整性校验、数字签名
如何选择合适的慢哈希?
推荐优先级:
Argon2 > bcrypt > PBKDF2(Argon2 是当前最安全的方案)
参数配置建议:
PBKDF2:迭代次数 ≥ 100,000 次
bcrypt:工作因子 ≥ 12
Argon2:time_cost≥3,memory_cost≥64MB,parallelism≥4
实际应用案例
1. 密码存储(数据库字段)
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
password_hash VARCHAR(100), -- 存储 bcrypt/Argon2 哈希值
salt VARCHAR(50) -- PBKDF2 需单独存盐
);
2. 用户注册/登录流程
# 注册时哈希密码
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!"
总结
慢哈希的核心目标:通过 降低计算速度 和 增加资源消耗,抵御密码破解永远不要用 MD5/SHA-1 存储密码,优先选择 Argon2 或 bcrypt必须加盐:防止彩虹表攻击,即使相同密码哈希结果也不同
加盐Hash
加盐 Hash 是一种密码安全存储技术,通过在原始密码 拼接随机数据(Salt,盐值) 后再进行哈希计算,使得即使相同的密码也会生成不同的哈希值,从而有效防御 彩虹表攻击 和 批量破解
为什么需要加盐?
问题:直接存储哈希值的风险
如果直接存储 Hash(password),攻击者可以通过以下方式破解:
彩虹表攻击:预计算常见密码的哈希值,直接反向查询批量破解:同一密码的哈希值相同,破解一个等于破解所有
示例(不安全存储):
用户名
密码(MD5)
Alice
5f4dcc3b5aa765d61d8327deb882cf99
(password)
Bob
5f4dcc3b5aa765d61d8327deb882cf99
(password)
👉 攻击者发现 Alice 和 Bob 的哈希相同,直接破解一次即可获取两人密码
解决方案:加盐(Salt)
盐值(Salt):一个 随机生成 的字符串(通常 16-32 字节),每个用户不同存储方式:盐值 + 哈希值一起存入数据库
加盐后的存储示例:
用户名
盐值(Salt)
密码哈希(Salt + Password)
Alice
a1b2c3...
(随机)
sha256("a1b2c3...password")
→ x1y2z3...
Bob
d4e5f6...
(随机)
sha256("d4e5f6...password")
→ p7q8r9...
即使密码相同,哈希值也不同,攻击者必须逐个破解!
加盐 Hash 的实现步骤
(1)用户注册时
生成随机盐值(每个用户唯一):
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...');
(2)用户登录时
从数据库取出盐值和哈希:
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("登录成功!")
加盐的核心作用
攻击方式
无盐哈希的风险
加盐哈希的防御效果
彩虹表攻击
直接查表破解
盐值使预计算表失效,必须重新生成
批量破解
破解一个哈希等于破解所有相同密码
每个用户的哈希不同,必须逐个破解
字典攻击
快速尝试常见密码
盐值增加密码复杂性,降低破解效率
加盐的最佳实践
盐值必须是唯一的盐值长度必须足够长,避免盐值碰撞盐值需与密码hash值一起存结合慢hash算法,单独加盐仍存在暴力破解的可能,建议配置bcrypt/Argon2/PBKDF2
为什么单独加盐仍然存在破解的可能
即使使用了唯一且随机的盐值,如果哈希算法本身速度过快(如 SHA-256、MD5),攻击者仍可以:
借助高性能硬件(GPU/ASIC)快速计算数十亿次哈希,尝试暴力破解。针对每个用户单独攻击(虽然盐值增加了成本,但不足以彻底阻止)
加盐 vs 其他安全措施
技术
作用
加盐的必要性
加盐(Salt)
防止彩虹表攻击和批量破解
✅ 必须
慢哈希
增加暴力破解成本
✅ 建议配合使用
密钥拉伸
通过多次迭代增加计算时间
✅ 建议配合使用
实际代码示例
(1)SHA-256(手动加盐+快速Hash算法)
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}")
(2)bcrypt-(自动加盐+慢Hash算法)
bcrypt可以从hash后字符串自动提取盐值,这样子用户下一次登陆时候的原始密码就能结合原始盐值进行hash运算得到原来的hash值
import bcrypt
password = "my_secure_password".encode()
hash = bcrypt.hashpw(password, bcrypt.gensalt()) # 自动加盐+慢哈希
print(f"Hash: {hash.decode()}")
总结
加盐 Hash = 密码 + 随机盐值 → 哈希计算核心作用:防御彩虹表攻击,确保相同密码哈希不同必须结合:随机盐(≥16 字节) + 慢哈希算法(如 bcrypt)用户盐分唯一:表中存储用户唯一的盐分,方便下一次查询出来的时候拼接在Hash判断密码正确性记住:md5(password) ❌ → bcrypt(salt + password) ✅
MD算法
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算法
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-256 算法的哈希值长度为 256 位,MD5 算法的哈希值长度为 128 位
这就提高了攻击者暴力破解或者彩虹表攻击的难度更强的碰撞抗性:SHA 算法采用了更复杂的运算过程和更多的轮次,使得攻击者更难以通过预计算或巧合找到碰撞
目前还没有找到任何两个不同的数据,它们的 SHA-256 哈希值相同
当然,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
慢Hash-Bcrypt
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();
}
慢Hash-Argon2
Argon2 是目前密码哈希领域的佼佼者
凭借其抗暴力破解能力和灵活的参数配置,成为许多安全场景的首选
它在 2015 年密码哈希竞赛中夺冠,如今已被广泛应用于各类系统中
核心优势在于能有效抵御GPU 加速攻击 和 内存 - hard 攻击
为什么需要 Argon2
在密码存储中,直接存储明文密码风险极大,而普通哈希算法(如 MD5、SHA-256)因计算速度快,容易被暴力破解工具(如彩虹表)攻破
Argon2 则通过故意设计的高计算成本和内存消耗,大幅提高破解难度,同时支持并行计算优化,平衡安全性与性能
Argon2 的三种类型及区别
Argon2 有三个变种,适用于不同场景,核心差异在于是否利用数据依赖和内存访问模式:
类型
特点
适用场景
Argon2d
数据依赖的内存访问,抗 GPU 攻击能力最强,但可能存在侧信道攻击风险
对侧信道攻击不敏感的场景(如离线密码哈希)
Argon2i
数据独立的内存访问,通过密码哈希保护内存访问模式,侧信道安全性更高
密码验证(如登录系统)
Argon2id
结合前两者优势,先使用 Argon2i 再用 Argon2d,平衡安全性和抗攻击能力
推荐的通用场景(如大多数应用程序)
普通应用(如用户登录、密码存储):直接用Argon2id,安全性和兼容性最平衡特殊离线场景(且能确保硬件安全):可选 Argon2d对侧信道攻击特别敏感的场景(如加密芯片中的密码验证):可选 Argon2i
注意:Argon2id 是目前最推荐的类型,在 RFC 9106 中被指定为标准,兼顾安全性和实用性
应用能力不同的底层原因是啥?
为什么普通应用(登录、密码存储)首选 Argon2id?
普通应用的威胁是 “两头都要防”:
一方面怕黑客用 GPU / 超级计算机暴力破解(比如批量试密码)另一方面怕有人通过偷偷监控设备(比如测电流、看内存访问规律)来猜密码(侧信道攻击)
而 Argon2id 是 “混合体”:先按 Argon2i 的方式防侧信道攻击,再按 Argon2d 的方式防 GPU 破解,等于 “兼顾了两种防护”。普通应用里,这两种威胁都可能存在,所以选它最稳妥
为什么特殊离线场景可选 Argon2d?
离线场景(比如本地存储的密码文件,不联网验证)的核心威胁是 “被黑客拿到文件后,用 GPU 暴力破解”。
Argon2d 抗 GPU 破解的能力最强,但它的内存访问路径和密码相关,容易被侧信道攻击抓漏洞
但 “离线场景” 通常是在你自己的设备上(比如加密的本地密码管理器),别人很难物理接触设备搞侧信道攻击(总不能撬开你电脑测电流吧),所以可以放心用它的 “强项”
为什么侧信道敏感场景选 Argon2i?
有些场景特别怕 “侧信道攻击”,比如加密芯片(银行 U 盾、智能卡)—— 这些设备体积小,容易被物理接触,黑客可能通过监测它工作时的耗电、发热变化来反推密码
Argon2i 的内存访问路径和密码无关,就算被监测,也泄露不了密码相关的规律,所以侧信道安全性最高。虽然它抗 GPU 破解的能力弱一点,但这类场景里,“防物理监测” 比 “防暴力破解” 更重要
一句话总结:
怕两种攻击都来?选 Argon2id(普通应用基本都这样)。只怕 GPU 暴力破解,不怕物理监测?选 Argon2d(离线场景)。最怕物理监测(侧信道攻击)?选 Argon2i(加密芯片等特殊设备)。
日常开发不用纠结,直接用 Argon2id,这是官方认证的 “万能款”
核心参数:如何配置 Argon2?
Argon2 的安全性通过以下参数控制,需根据系统性能和安全需求调整:
时间成本(t):迭代次数(哈希计算的轮数),增加计算时间内存成本(m):以 KiB 为单位的内存消耗(如 65536 = 64MB),提高内存需求并行度(p):并行计算的线程数,利用多核处理器优化性能,不影响安全性盐(salt):随机生成的 16 字节以上字符串,每个密码对应唯一盐值,防止彩虹表攻击哈希长度(hash length):输出的哈希值长度(通常 16-32 字节)
示例配置(适用于多数应用):t=3, m=65536, p=4,即 3 轮迭代、64MB 内存、4 线程并行
实际应用:如何使用 Argon2?
大多数编程语言都有 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算法简单总结
什么是Hash算法
哈希算法的是不可逆的,你无法通过哈希之后的值再得到原值
Hash算法分类:
加密Hash算法:安全性相对较高,但性能较差。适用于数据完整性校验非加密Hash算法:安全性相对较低的哈希算法,但性能较好。适用于普通Hash,例如数据分片,Hash槽的Hash表特殊Hash算法:安全性更高的慢Hash,增大攻击者的时间成本和设备的内存成本
什么是彩虹表
彩虹表 是一种预先计算好的 哈希值与明文密码的映射表,用于快速破解哈希算法加密的密码
它通过 时间-存储权衡 大幅减少暴力破解所需的时间
尤其对弱密码或未加盐(Unsalted)的哈希攻击效果极强
可以通过加盐和慢Hash增大攻击成本,阻止彩虹表的暴力破解
MD5目前就已经被彩虹表暴力破解了,所以现在不推荐用MD5了,重要数据Hash场景更不该用,用的话要加盐
为什么需要加盐
防止下面两个问题:
彩虹表攻击:预计算常见密码的哈希值,直接反向查询批量破解:同一密码的哈希值相同,破解一个等于破解所有
市面上的加密算法都有彩虹表算出对应的值,不加盐的话容易被猜出来
如果不加盐,破解出了一个密码,攻击者发现 Alice 和 Bob 的哈希相同,直接破解一次即可获取两人密码
盐值必须是用户唯一的且随机生成的盐值长度必须足够长,避免盐值碰撞盐值需与密码hash值一起存结合慢hash算法,单独加盐仍存在暴力破解的可能,建议配置bcrypt/Argon2/PBKDF2
加密算法SHA256(快+不碰撞)
又快又安全,碰撞概率极低
SHA-256 算法的哈希值长度为 256 位,MD5 算法的哈希值长度为 128 位
这就提高了攻击者暴力破解或者彩虹表攻击的难度目前还没有找到任何两个不同的数据,它们的 SHA-256 哈希值相同
场景:比如我们日常下载软件安装包时,官方通常会提供该安装包的 SHA256 哈希值
这种场景要求性能快和不碰撞,这种hash值破解了也几乎没啥用
非加密Hash(快+不追求低碰撞)
例如普通的模运算
特点:
计算速度快(比加密哈希快 10-100 倍)不保证抗碰撞性(可能被恶意构造碰撞)不保证单向性(可能被逆向工程)
场景:
哈希表:Java 的 HashMap 使用 hashCode()分布式系统:分布式系统中,不同节点之间同步数据时,通过计算数据的 SHA256 哈希值,可以快速比对各节点数据是否一致缓存键生成:Redis 的键哈希数据分片:如数据库分库分表
慢Hash(密码存储)
核心特点:故意降低计算速度,以抵御暴力破解和彩虹表攻击
通常结合加盐和多次迭代大幅增加攻击者的破解成本
慢Hash的目的:
让合法用户验证密码时稍微慢一点,但让攻击者破解密码的成本高到无法承受
慢哈希通过 人为引入计算延迟,使得攻击者需要数年甚至更长时间才能破解一个密码
慢Hash算法:
bcryptArgon2
底层都有自动加盐,目前推荐Argon2,它在 2015 年密码哈希竞赛中夺冠
bcypt只能从时间层面加大成本,但是Argon2能在时间+内存空间层面加大成本
MD5和SHA-256
推荐使用SHA-256
MD5已经被彩虹表破解
SHA-256适用于数据校验场景
SHA-256 算法的哈希值长度为 256 位,MD5 算法的哈希值长度为 128 位
这就提高了攻击者暴力破解或者彩虹表攻击的难度目前还没有找到任何两个不同的数据,它们的 SHA-256 哈希值相同
慢Hash-Bcrypt和Argon2
bcrypt 是慢哈希算法,且在很长一段时间内是密码存储的优选方案
但随着硬件技术的发展,其 【仅依赖计算耗时】 的设计逐渐显现短板
而 Argon2 等结合了计算成本和内存成本的算法,在抗现代攻击方面更具优势
不过,对于安全性要求不极致且需兼顾兼容性的场景,bcrypt 仍是可靠选择
bcrypt和Argon2底层都自动加盐
Bcrypt
Bcrypt 采用了 salt(盐) 和 cost(成本) 两种机制,它可以有效地防止彩虹表攻击和暴力破解攻击,从而保证密码的安全性
salt: 是一个随机生成的字符串,用于和密码混合,增加密码的复杂度和唯一性
cost :是一个数值参数,用于控制 Bcrypt 算法的迭代次数,增加密码哈希的计算时间和资源消耗
Argon2
Argon2 则通过故意设计的高计算成本和内存消耗,大幅提高破解难度,同时支持并行计算优化,平衡安全性与性能
黑客可以通过暴力运算破解和侧信道攻击(通过物理设备查看电流和内存规律推导出密码)
三种Argon2
Argon2d:防暴力运算,但是侧信道攻击难防。适用于离线场景,离线场景通常是自己的设备,别人很难物理接触设备搞侧信道攻击Argon2i:防侧信道攻击,但是暴力运算难防。适用于体积小的物理设备,例如芯片,智能卡等物理设备,黑客可能通过监测它工作时的耗电、发热变化来反推密码Argon2id:两者都兼容
一般场景推荐使用Argon2id
Argon2 的安全性通过以下参数控制,需根据系统性能和安全需求调整:
时间成本(t):迭代次数(哈希计算的轮数),增加计算时间内存成本(m):以 KiB 为单位的内存消耗(如 65536 = 64MB),提高内存需求并行度(p):并行计算的线程数,利用多核处理器优化性能,不影响安全性盐(salt):随机生成的 16 字节以上字符串,每个密码对应唯一盐值,防止彩虹表攻击哈希长度(hash length):输出的哈希值长度(通常 16-32 字节)
示例配置:t=3, m=65536, p=4,即 3 轮迭代、64MB 内存、4 线程并行