本文面向系统架构师、后端开发者和安全工程师,系统对比当前主流的密码哈希算法(BCrypt、Argon2、scrypt、PBKDF2),从设计原理、安全性、性能、参数配置到实战选型,提供一份可落地的技术决策参考。

📑 目录


前言

密码存储是应用安全的第一道防线。然而,仍有大量系统在使用 MD5、SHA-1 甚至明文存储密码。密码哈希的核心原则是“慢” —— 哈希速度越慢,攻击者暴力破解的成本就越高。

目前业界公认的主流密码哈希算法有四种:Argon2、BCrypt、scrypt、PBKDF2。其中 Argon2 是 2015 年密码哈希竞赛(PHC)的冠军,被 OWASP 和 NIST 共同推荐为首选方案。


1. 为什么密码需要“慢哈希”?

密码哈希与数据完整性哈希(如 SHA-256)有本质区别:

特性数据完整性哈希密码哈希
速度要求越快越好越慢越好
设计目标快速验证数据完整性抵抗暴力破解
典型算法MD5, SHA-1, SHA-256Argon2, BCrypt, scrypt
抗GPU/ASIC不关注核心设计目标

攻击者可以利用 GPU、FPGA 或 ASIC 进行每秒数亿次的哈希计算。密码哈希算法通过可调节的工作因子(计算成本、内存成本、并行度)来对抗硬件算力的增长。


2. 四大算法全景概览

算法发布年份内存硬化抗GPU抗侧信道当前推荐度
Argon2id2015✅ 是✅ 是✅ 是⭐⭐⭐⭐⭐ 首选
BCrypt1999❌ 否⚠️ 部分✅ 是⭐⭐⭐ 遗留/兼容
scrypt2009✅ 是✅ 是⚠️ 部分⭐⭐⭐⭐ 备选
PBKDF22000❌ 否❌ 否✅ 是⭐⭐ FIPS合规场景

内存硬化(Memory-Hard)指算法需要大量内存才能计算,这使得攻击者难以用 GPU/ASIC 进行大规模并行破解。


3. 各算法深度解析

3.1 Argon2 —— 现代密码哈希的黄金标准

Argon2 在 RFC 9106 中定义,标题为“用于密码哈希和工作量证明应用的内存硬函数”。它是密码哈希竞赛(PHC)的获胜者,被 OWASP 2025NIST SP 800-63B 共同推荐。

三种变体
变体特点适用场景
Argon2d数据相关内存访问,最大化抗GPU能力加密货币、工作量证明
Argon2i数据独立内存访问,抗侧信道攻击密码哈希、密钥派生
Argon2id混合模式(前半段 Argon2i + 后半段 Argon2d)密码存储首选

⚠️ OWASP 明确建议:密码存储场景应使用 Argon2id,而非 Argon2 的其他变体。

核心优势
  • 内存可调:通过内存成本参数(m)抵御 ASIC 攻击
  • 时间可调:通过迭代次数(t)抵御算力增长
  • 并行可调:通过并行度(p)充分利用多核 CPU
哈希格式示例
$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$eS5Dj3JD7U3AGAvc5UHE4f+lgjxuVqHkRCk70Yg3GoA

各部分含义:算法类型 | 版本 | 参数(m,t,p) | Salt(Base64) | Hash输出( Base64)


3.2 BCrypt —— 久经考验的经典之选

BCrypt 由 Niels Provos 和 David Mazières 于 1999 年基于 Blowfish 密码设计。它是过去二十年间最广泛使用的密码哈希算法。

核心机制
  • 基于 Blowfish 分组密码的 Eksblowfish 算法
  • 内置 自动加盐(22 字符随机盐)
  • 通过 工作因子(cost factor) 控制迭代次数((2^{cost}) 轮)
已知限制
  • 72 字节密码长度限制:超过 72 字节的密码会被静默截断
  • 内存使用固定 4KB:无法通过增加内存来提升抗 ASIC 能力
  • 输出长度固定 60 字符

⚠️ CVE-2025-25298 显示,Strapi 因未对密码长度做上限校验,导致使用 bcryptjs 时密码被截断,产生认证绕过漏洞。


3.3 scrypt —— 内存硬化的先行者

scrypt 于 2009 年发布,是最早广泛采用内存硬化技术的密码哈希算法之一。

核心机制
  • 通过 N(CPU/内存成本)、r(块大小)、p(并行度)三个参数控制
  • 需要大量内存,显著增加 ASIC 攻击成本
局限性
  • 参数调优复杂:N、r、p 三个参数相互影响,调优门槛高
  • 生态支持相对较弱:不如 BCrypt 和 Argon2 普及

3.4 PBKDF2 —— FIPS合规的常青树

PBKDF2(基于口令的密钥派生函数 2)在 RFC 2898 中定义。

典型应用
  • WPA-2 Wi-Fi 密码生成
  • TrueCrypt 磁盘加密的密钥派生
  • FIPS-140 合规要求的政府/金融系统
核心缺陷
  • 非内存硬化:无法抵御 ASIC/GPU 攻击
  • 依赖底层哈希:若使用 SHA-1 等弱哈希,安全性会进一步降低

4. 全方位对比表格

对比维度Argon2idBCryptscryptPBKDF2
设计年代2015(PHC冠军)199920092000
内存硬化✅ 可调(MB~GB)❌ 固定4KB✅ 可调
抗GPU/ASIC✅ 优秀⚠️ 一般✅ 良好❌ 差
抗侧信道✅ 优秀✅ 良好⚠️ 一般✅ 良好
内置加盐✅ 是✅ 是✅ 是⚠️ 需手动
密码长度限制❌ 无⚠️ 72字节❌ 无❌ 无
参数可调性3维(内存/时间/并行)1维(工作因子)3维(N/r/p)1维(迭代次数)
OWASP 2025推荐首选⚠️ 仅遗留系统✅ 备选❌ 不推荐
NIST推荐✅ 推荐✅ 推荐✅ 推荐✅ FIPS场景
生态成熟度⭐⭐⭐⭐ 快速增长⭐⭐⭐⭐⭐ 最成熟⭐⭐⭐ 中等⭐⭐⭐⭐⭐ 最成熟
哈希输出格式PHC标准(变长)固定60字符变长变长

5. 性能与安全性深度分析

5.1 性能对比(同等安全级别)

一项 2025 年的学术研究对比了 Argon2id 和 BCrypt 的默认参数:

指标Argon2idBCrypt
处理速度更快较慢
存储空间更小较大
内存消耗较高(128KB+)更低(96KB)
雪崩效应更高较低
输出长度较短更长

研究结论:Argon2id 在性能上全面优于 BCrypt,而两者都提供良好的安全性

5.2 安全性深度分析

攻击向量Argon2idBCryptscryptPBKDF2
暴力破解✅ 时间+内存成本双重防护✅ 时间成本防护✅ 时间+内存成本防护⚠️ 仅时间成本
GPU加速攻击✅ 内存硬化有效抵御⚠️ 4KB内存可被GPU高效计算✅ 内存硬化有效抵御❌ 极易被GPU加速
ASIC定制攻击✅ 内存成本高,ASIC设计复杂❌ 4KB内存ASIC成本低✅ 内存成本高❌ ASIC可实现极高速度
侧信道攻击✅ Argon2id混合设计✅ 良好⚠️ 一般✅ 良好
彩虹表攻击✅ 内置盐✅ 内置盐✅ 内置盐⚠️ 需手动加盐

5.3 实际攻击成本对比(OWASP 2025数据)

OWASP 推荐的 46 MiB Argon2id 配置可将账户被攻破率比 SHA-256 降低 42.5%(按 $1/账户 的攻击预算计算)。


6. 选型决策指南

开始选型

是否有FIPS-140合规要求?

PBKDF2
迭代次数≥600,000

是否有遗留系统兼容需求?

BCrypt
工作因子≥10

是否追求最高安全性?

Argon2id
OWASP 2025首选

是否需要内存硬化?

scrypt
参数N=2^17

BCrypt
工作因子≥12

6.1 各场景推荐

场景推荐算法理由
新项目(通用)Argon2idOWASP/NIST双推荐,最安全
金融/医疗/高安全Argon2id内存硬化 + 可调参数,抵御ASIC
政府/FIPS合规PBKDF2FIPS-140认证要求
遗留系统升级BCrypt → Argon2id渐进式迁移,登录时自动重哈希
嵌入式/资源受限BCrypt内存占用小(4KB)
加密货币/区块链Argon2d数据相关访问,抗GPU最强

7. 参数配置最佳实践

7.1 Argon2id 推荐参数

来源内存(m)迭代(t)并行§盐长度输出长度
OWASP 2025(高安全)64 MB3416 bytes32 bytes
OWASP(最低)19 MiB2116 bytes32 bytes
RFC 9106最大可用内存1-3-16+ bytes32 bytes
实际生产建议46-64 MB3CPU核心数(≤4)16 bytes32 bytes

💡 目标哈希时间:建议调整内存成本,使单次哈希耗时在 150-250ms 之间。

7.2 BCrypt 推荐参数

场景工作因子说明
最低安全10约 2^10 = 1024 轮
生产推荐12平衡安全与性能
高安全14+单次哈希可能超过 500ms

⚠️ BCrypt 密码长度限制 72 字节,建议在应用层对密码长度做上限校验。

7.3 scrypt 推荐参数

参数推荐值说明
N2^17 (128 MiB)CPU/内存成本
r8块大小
p1并行度

7.4 PBKDF2 推荐参数(FIPS场景)

参数推荐值
底层哈希HMAC-SHA-256
迭代次数600,000 或更高
盐长度16+ bytes

8. 各语言实现示例

8.1 Rust(使用 argon2 crate)

// Cargo.toml
// [dependencies]
// argon2 = "0.5"
// anyhow = "1.0"

use argon2::{
    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
    Argon2, Version, Params,
};
use anyhow::Result;

pub fn hash_password(password: &str) -> Result<String> {
    // 生成随机盐(16字节)
    let salt = SaltString::generate(&mut rand::thread_rng());
    
    // Argon2id 配置:64MB内存,3次迭代,4路并行
    let params = Params::new(65536, 3, 4, Some(32))?; // m=64MB, t=3, p=4
    let argon2 = Argon2::new(
        argon2::Algorithm::Argon2id,
        argon2::Version::V0x13,
        params,
    );
    
    let password_hash = argon2.hash_password(password.as_bytes(), &salt)?;
    Ok(password_hash.to_string())
}

pub fn verify_password(password: &str, hash: &str) -> Result<bool> {
    let parsed_hash = PasswordHash::new(hash)?;
    let argon2 = Argon2::default();
    Ok(argon2.verify_password(password.as_bytes(), &parsed_hash).is_ok())
}

8.2 Rust(使用 bcrypt crate)

// Cargo.toml
// [dependencies]
// bcrypt = "0.15"

use bcrypt::{hash, verify, DEFAULT_COST};

pub fn hash_password_bcrypt(password: &str) -> Result<String, bcrypt::BcryptError> {
    // 工作因子 12
    let hashed = hash(password, 12)?;
    Ok(hashed)
}

pub fn verify_password_bcrypt(password: &str, hash: &str) -> Result<bool, bcrypt::BcryptError> {
    verify(password, hash)
}

8.3 TypeScript/Node.js(使用 @node-rs/argon2

import { hash, verify } from '@node-rs/argon2';

// 哈希密码
const passwordHash = await hash('user_password', {
    memoryCost: 65536, // 64 MB
    timeCost: 3,       // 3 次迭代
    parallelism: 4,    // 4 线程
    outputLen: 32,     // 32 字节输出
});

// 验证密码(使用恒定时间比较,防止时序攻击)
const isValid = await verify(passwordHash, 'user_password');
console.log(isValid); // true 或 false

参考实现

8.4 Python(使用 argon2-cffi

from argon2 import PasswordHasher
from argon2.exceptions import VerificationError

ph = PasswordHasher(
    memory_cost=65536,  # 64 MB
    time_cost=3,        # 3 次迭代
    parallelism=4,      # 4 线程
    hash_len=32,        # 32 字节
)

# 哈希
hash = ph.hash("user_password")

# 验证
try:
    ph.verify(hash, "user_password")
    print("Password is correct!")
except VerificationError:
    print("Password is incorrect!")

参考实现

8.5 Go(使用标准库 crypto/argon2

package main

import (
    "crypto/rand"
    "crypto/subtle"
    "encoding/base64"
    "fmt"
    "golang.org/x/crypto/argon2"
)

type Argon2Config struct {
    Memory      uint32
    Iterations  uint32
    Parallelism uint8
    SaltLength  uint32
    KeyLength   uint32
}

var DefaultConfig = Argon2Config{
    Memory:      64 * 1024, // 64 MB
    Iterations:  3,
    Parallelism: 4,
    SaltLength:  16,
    KeyLength:   32,
}

func HashPassword(password string, config *Argon2Config) (string, error) {
    salt := make([]byte, config.SaltLength)
    if _, err := rand.Read(salt); err != nil {
        return "", err
    }

    hash := argon2.IDKey(
        []byte(password),
        salt,
        config.Iterations,
        config.Memory,
        config.Parallelism,
        config.KeyLength,
    )

    // PHC格式编码
    encoded := fmt.Sprintf(
        "$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s",
        config.Memory,
        config.Iterations,
        config.Parallelism,
        base64.RawStdEncoding.EncodeToString(salt),
        base64.RawStdEncoding.EncodeToString(hash),
    )
    return encoded, nil
}

参考实现


9. 常见问题与踩坑记录

❌ 错误1:使用 MD5/SHA-1/SHA-256 存储密码

问题:这些是快速哈希,攻击者可用 GPU 每秒计算数十亿次。
解决:立即迁移到 Argon2id 或 BCrypt。

❌ 错误2:BCrypt 密码被静默截断

问题:BCrypt 只处理前 72 字节,超长密码会被截断。
解决

  • 在应用层限制密码最大长度(如 72 字节)
  • 或使用 Argon2id(无长度限制)

❌ 错误3:Argon2 参数配置过低

问题:46.6% 的 Argon2 部署使用了弱于 OWASP 推荐的参数。
解决:使用 OWASP 推荐的最低配置(19 MiB / 2 次迭代)或更高。

❌ 错误4:未使用恒定时间比较验证密码

问题:使用 == 比较哈希值可能被时序攻击利用。
解决:使用库提供的 verify 函数,它们都实现了恒定时间比较。

❌ 错误5:所有用户使用相同的盐

问题:彩虹表攻击可以破解所有相同密码的用户。
解决:使用库的默认行为——每个密码生成唯一随机盐

❌ 错误6:工作因子固定不变

问题:随着硬件算力增长,固定的工作因子会逐渐失效。
解决:定期评估并提高工作因子,在用户登录时用新参数重新哈希。


10. 总结

你的需求最佳选择
新项目,追求最高安全性Argon2id(64MB / 3次迭代 / 4并行)
需要FIPS合规PBKDF2(HMAC-SHA-256 / 600,000次迭代)
遗留系统或资源受限BCrypt(工作因子 12+)
需要内存硬化但Argon2不可用scrypt(N=2^17 / r=8 / p=1)

核心建议

  1. 新项目首选 Argon2id:OWASP 2025 和 NIST 共同推荐
  2. BCrypt 仅用于遗留系统:注意 72 字节密码限制
  3. 定期调整工作因子:随硬件发展提升参数
  4. 永远不要自创哈希算法:使用经过同行评审的标准算法

如果本文对你有帮助,欢迎点赞👍、收藏⭐、关注🔔!有任何问题欢迎在评论区交流💬。
下一篇我将分享《Rust + Argon2id 生产级密码管理模块设计与实现》,敬请期待!

标签密码安全 Argon2 BCrypt scrypt PBKDF2 OWASP 身份认证 Rust 安全开发 哈希算法

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐