← 返回博客

如何安全地哈希存储用户密码

2026-04-08 · 5 分钟阅读

绝对不要用 MD5 或 SHA256 存储密码

这是密码安全中最重要的一条规则:MD5、SHA1、SHA256 等通用哈希函数都不应该用于密码存储。原因是它们设计得太快了——每秒可以计算数十亿次哈希。这意味着攻击者拿到哈希后,可以在很短时间内通过暴力搜索或字典攻击找到原始密码。2012 年 LinkedIn 的数据泄露中,约 60% 的 SHA1 密码哈希在几天内被破解,因为许多用户使用了弱密码。

密码哈希的特殊要求

安全的密码存储需要满足三个核心要求:

bcrypt:经典的密码哈希函数

bcrypt 由 Niels Provos 和 David Mazières 于 1999 年设计,是目前使用最广泛的密码哈希函数。它内置了盐值和代价因子(Cost Factor),通过 cost 参数控制计算复杂度:

// Node.js (using bcryptjs or bcrypt package)
const bcrypt = require('bcryptjs');

// Hashing a password
async function hashPassword(password) {
  const saltRounds = 12; // Cost factor: 2^12 = 4096 iterations
  const hash = await bcrypt.hash(password, saltRounds);
  return hash;
  // Returns: "$2b$12$[22-char salt][31-char hash]"
}

// Verifying a password
async function verifyPassword(password, hash) {
  const match = await bcrypt.compare(password, hash);
  return match; // true or false
}

// Usage
const hash = await hashPassword('mypassword123');
// Store hash in database

const isValid = await verifyPassword('mypassword123', hash);
// true

cost 参数建议:对于大多数应用,12–14 是合适的选择(使哈希时间约为 100–300ms)。随着硬件升级,可以逐步提高此值。

Argon2:现代密码哈希的首选

Argon2 是 2015 年密码哈希竞赛(PHC)的获胜者,被认为是目前最安全的密码哈希算法。Argon2 有三个变体:Argon2i(抗侧信道攻击)、Argon2d(抗 GPU 攻击)、Argon2id(两者的结合,推荐)。与 bcrypt 相比,Argon2 还可以配置内存使用量,使其更难通过专用硬件(ASIC)并行破解。

// Python (using argon2-cffi)
from argon2 import PasswordHasher

ph = PasswordHasher(
    time_cost=2,          # Iterations
    memory_cost=65536,    # 64 MB RAM usage
    parallelism=2,        # Parallel threads
    hash_len=32,          # Hash output length
    salt_len=16           # Salt length
)

# Hash
hash = ph.hash("mypassword123")
# Returns: "$argon2id$v=19$m=65536,t=2,p=2$..."

# Verify
try:
    ph.verify(hash, "mypassword123")
    print("Password correct")
except:
    print("Password incorrect")

PBKDF2:广泛支持的标准方案

PBKDF2(Password-Based Key Derivation Function 2)是 NIST 推荐且 FIPS 140-2 认可的密码哈希方案。虽然安全性不如 bcrypt 和 Argon2(没有内存复杂度),但在需要合规认证的环境中非常有用:

// Node.js (built-in crypto module)
const crypto = require('crypto');
const util   = require('util');
const pbkdf2 = util.promisify(crypto.pbkdf2);

async function hashPassword(password) {
  const salt = crypto.randomBytes(32).toString('hex');
  const hash = await pbkdf2(password, salt, 310000, 32, 'sha256');
  return `${salt}:${hash.toString('hex')}`;
}

async function verifyPassword(password, storedHash) {
  const [salt, hash] = storedHash.split(':');
  const newHash = await pbkdf2(password, salt, 310000, 32, 'sha256');
  return crypto.timingSafeEqual(Buffer.from(hash, 'hex'), newHash);
}

时序安全比较

在验证密码(或任何秘密值)时,必须使用时序安全(Constant-Time)的比较函数,而不是普通的字符串相等比较(如 =====)。普通字符串比较在第一个不匹配字符处就会返回,攻击者可以通过测量响应时间来推断哈希值的内容(时序攻击)。Node.js 提供了 crypto.timingSafeEqual(),bcrypt 的 compare() 也内置了时序安全比较。

密码哈希最佳实践总结

立即免费使用相关工具

免费使用 →