pbkdf2 vs bcrypt vs crypto-js vs scrypt-js
前端密码哈希与密钥派生库选型指南
pbkdf2bcryptcrypto-jsscrypt-js类似的npm包:

前端密码哈希与密钥派生库选型指南

bcrypt、crypto-js、pbkdf2 和 scrypt-js 都是用于密码哈希或密钥派生的 JavaScript 库,适用于需要在客户端安全处理用户凭证的场景。bcrypt 实现 bcrypt 算法,crypto-js 是一个通用加密库(包含 PBKDF2),pbkdf2 专注于 PBKDF2 密钥派生函数,而 scrypt-js 则实现了内存密集型的 scrypt 算法。这些库在算法选择、浏览器兼容性、API 设计和安全模型上存在显著差异,直接影响前端架构中的安全性与性能权衡。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
pbkdf216,378,744203-34 个月前MIT
bcrypt6,976,1597,8051.11 MB381 年前MIT
crypto-js016,409487 kB2793 年前MIT
scrypt-js0145-136 年前MIT

前端密码哈希方案深度对比:bcrypt、crypto-js、pbkdf2 与 scrypt-js

在现代 Web 应用中,安全地处理用户密码至关重要。虽然理想情况下密码哈希应在服务端完成,但某些场景(如客户端预哈希、离线应用、零知识证明等)仍需在前端执行密码派生。本文深入比较四个主流 npm 包 —— bcrypt、crypto-js、pbkdf2 和 scrypt-js —— 从算法特性、API 设计、浏览器兼容性到实际使用限制,帮助前端架构师做出合理选择。

🔐 核心算法与安全模型

bcrypt 实现的是 Blowfish 密码哈希算法的变种,通过自适应的计算成本(cost factor)抵御暴力破解。它内置盐值生成,输出包含算法标识、成本参数、盐和哈希值,格式为 $2a$10$...。

// bcrypt (需注意:官方包主要面向 Node.js)
import bcrypt from 'bcrypt';

// 注意:在纯前端环境中,此包依赖 WebAssembly 或 polyfill
const saltRounds = 10;
const hash = await bcrypt.hash('myPassword', saltRounds);
const isValid = await bcrypt.compare('myPassword', hash);

crypto-js 是一个通用加密库,支持多种算法(AES、SHA 等),其 PBKDF2 实现基于 HMAC-SHA1/SHA256。但它不提供 bcrypt 或 scrypt。

// crypto-js 的 PBKDF2
import CryptoJS from 'crypto-js';

const salt = CryptoJS.lib.WordArray.random(128/8);
const key = CryptoJS.PBKDF2('myPassword', salt, {
  keySize: 256/32,
  iterations: 1000,
  hasher: CryptoJS.algo.SHA256
});
// 输出为 WordArray,需 toString() 转换

pbkdf2 是 Node.js 内置 crypto.pbkdf2 的浏览器兼容封装,仅聚焦于 PBKDF2 算法,支持 SHA-1、SHA-256 等 HMAC 哈希函数。

// pbkdf2
import { pbkdf2 } from 'pbkdf2';

pbkdf2('myPassword', 'randomSalt', 1000, 32, 'sha256', (err, derivedKey) => {
  if (err) throw err;
  console.log(derivedKey.toString('hex'));
});
// 也支持 Promise 风格(需查看文档确认)

scrypt-js 实现了 scrypt 密钥派生函数,由 Colin Percival 提出,通过内存硬度(memory-hardness)显著增加 ASIC/FPGA 攻击成本,比 bcrypt 和 PBKDF2 更抗硬件加速破解。

// scrypt-js
import { scrypt, scryptSync } from 'scrypt-js';

const password = new TextEncoder().encode('myPassword');
const salt = new Uint8Array(32); // 应使用 CSPRNG 填充
const N = 16384; // CPU/内存成本
const r = 8;     // 块大小
const p = 1;     // 并行度

const key = await scrypt(password, salt, N, r, p, 32);
console.log(Buffer.from(key).toString('hex'));

🖥️ 浏览器兼容性与运行时要求

  • bcrypt:官方 bcrypt 包主要为 Node.js 设计。在浏览器中使用需额外构建步骤或依赖如 @types/bcrypt + Webpack 配置,或改用社区 fork(如 bcryptjs)。不推荐在新前端项目中直接使用 bcrypt 包,因其对浏览器支持有限且体积较大。
  • crypto-js:纯 JavaScript 实现,无外部依赖,可在任何现代浏览器中运行,打包友好。
  • pbkdf2:通过 polyfill 模拟 Node.js 的 crypto 模块,在浏览器中表现稳定,兼容性良好。
  • scrypt-js:纯 JavaScript 实现,无原生依赖,专为浏览器环境优化,支持 async/await。

⚠️ 重要提示:截至最新文档,bcrypt npm 包(由 kelektiv 维护)明确说明其“主要用于 Node.js”,前端开发者应优先考虑 bcryptjs(非本列表)或其他替代方案。

⚙️ API 风格与易用性

  • bcrypt:提供 hash() / compare() 等高层 API,但异步操作在浏览器中可能阻塞主线程(因无 Web Worker 支持)。
  • crypto-js:链式 API,但类型系统较弱(输出为 WordArray),需手动转换为字符串或 Buffer。
  • pbkdf2:回调风格为主,部分版本支持 Promise,需注意错误处理。
  • scrypt-js:同时提供同步 (scryptSync) 和异步 (scrypt) 版本,后者可避免 UI 卡顿,适合高成本参数场景。

🛡️ 安全建议与适用场景

何时使用 PBKDF2?

当需要符合 FIPS 标准或与现有系统(如 LDAP、WPA2)兼容时,PBKDF2 仍是可靠选择。pbkdf2 和 crypto-js 均可胜任,但 pbkdf2 更专注、更接近 Node.js 行为。

何时使用 scrypt?

若安全需求极高(如加密货币钱包、敏感数据保护),且可接受较长的派生时间,scrypt 的内存硬度提供更强保障。scrypt-js 是前端唯一成熟实现。

为何慎用 bcrypt?

尽管 bcrypt 广泛使用,但其在浏览器中的实现通常性能较差(JavaScript 模拟 Blowfish),且 bcrypt 包本身非为前端设计。若必须使用,应评估 bcryptjs(更轻量、纯 JS)。

crypto-js 的定位?

作为通用加密工具箱,适合需要多种算法(如 AES 加密 + PBKDF2 派生)的场景,但若仅需密码哈希,专用库更简洁安全。

🧪 性能与用户体验权衡

所有密码派生函数均为故意慢速设计。在前端执行时:

  • 高迭代次数(PBKDF2)或高 N 值(scrypt)会导致 UI 卡顿。
  • 务必使用异步版本(如 scrypt-js 的 scrypt())或将计算移至 Web Worker。
  • 避免在主线程执行高成本操作,影响交互流畅性。
// 推荐:在 Web Worker 中运行 scrypt
// worker.js
import { scrypt } from 'scrypt-js';
self.onmessage = async (e) => {
  const { password, salt, N, r, p, dkLen } = e.data;
  const key = await scrypt(password, salt, N, r, p, dkLen);
  self.postMessage(key);
};

📌 总结建议

包名推荐场景前端友好度安全强度
bcrypt不推荐用于新前端项目;优先考虑 bcryptjs❌ 低中
crypto-js需要多种加密功能(非仅密码哈希)✅ 高中(PBKDF2)
pbkdf2需标准 PBKDF2 且追求轻量、兼容 Node.js 行为✅ 高中
scrypt-js高安全需求、可接受性能开销、需内存硬度防护✅ 高高

最终建议:

  • 若仅需密码哈希,优先考虑 scrypt-js(高安全)或 pbkdf2(标准兼容)。
  • 避免在前端直接使用 bcrypt 包;如需 bcrypt 算法,请评估 bcryptjs。
  • 使用 crypto-js 仅当你同时需要其他加密功能(如对称加密)。
  • 所有前端密码操作都应视为辅助措施 —— 服务端必须再次哈希并验证。

如何选择: pbkdf2 vs bcrypt vs crypto-js vs scrypt-js

  • pbkdf2:

    选择 pbkdf2 如果你需要标准的、FIPS 兼容的 PBKDF2 实现,且希望行为与 Node.js 内置 crypto.pbkdf2 一致。它轻量、专注、浏览器兼容性好,适合需要与后端 PBKDF2 逻辑对齐或满足合规要求(如金融、政府系统)的前端应用。

  • bcrypt:

    不建议在新的前端项目中直接使用 bcrypt 包,因其主要面向 Node.js 环境,浏览器支持有限且性能不佳。若确实需要 bcrypt 算法,请评估社区维护的纯 JavaScript 替代品如 bcryptjs。仅在必须与现有 bcrypt 哈希兼容且无法修改后端逻辑时才考虑此路径。

  • crypto-js:

    选择 crypto-js 如果你需要一个多功能加密工具箱,不仅限于密码哈希(例如还需 AES 加密、HMAC 等)。它的 PBKDF2 实现可用,但若仅需密钥派生,专用库如 pbkdf2 或 scrypt-js 更轻量、更安全。适合已有项目已集成该库或需要多种加密原语的场景。

  • scrypt-js:

    选择 scrypt-js 如果你的应用对安全性要求极高(如加密钱包、零知识应用),且能接受较高的计算和内存开销。scrypt 的内存硬度使其比 bcrypt 和 PBKDF2 更抗硬件加速攻击。该库专为浏览器优化,提供异步 API,适合现代前端安全架构。

pbkdf2的README

pbkdf2 Version Badge

This library provides the functionality of PBKDF2 with the ability to use any supported hashing algorithm returned from crypto.getHashes()

github actions coverage dependency status dev dependency status License Downloads

npm badge

Usage

var pbkdf2 = require('pbkdf2')
var derivedKey = pbkdf2.pbkdf2Sync('password', 'salt', 1, 32, 'sha512')

...

For more information on the API, please see the relevant Node documentation.

For high performance, use the async variant (pbkdf2.pbkdf2), not pbkdf2.pbkdf2Sync; this variant has the oppurtunity to use window.crypto.subtle when browserified.

Credits

This module is a derivative of cryptocoinjs/pbkdf2-sha256, so thanks to JP Richardson for laying the ground work.

Thank you to FangDun Cai for donating the package name on npm, if you're looking for his previous module it is located at fundon/pbkdf2.