bcrypt vs crypto-js vs node-forge vs sjcl
前端加密与密码哈希库选型指南
bcryptcrypto-jsnode-forgesjcl类似的npm包:

前端加密与密码哈希库选型指南

bcrypt、crypto-js、node-forge 和 sjcl 都是 JavaScript 生态中常用的加密工具库,但它们的设计目标和适用场景截然不同。bcrypt 专用于密码哈希,通常运行在服务端;crypto-js 提供广泛的加密算法但性能一般;node-forge 功能最全面,支持 PKI 和证书操作;sjcl 则侧重于安全正确性但更新较慢。前端开发者在选择时需特别注意浏览器兼容性与安全风险。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
bcrypt07,8041.11 MB381 年前MIT
crypto-js016,409487 kB2793 年前MIT
node-forge05,3321.65 MB4656 个月前(BSD-3-Clause OR GPL-2.0)
sjcl07,1972.24 MB1186 个月前(BSD-2-Clause OR GPL-2.0-only)

前端加密库深度对比:bcrypt vs crypto-js vs node-forge vs sjcl

在 JavaScript 项目中处理加密需求时,开发者常面临库选型难题。bcrypt、crypto-js、node-forge 和 sjcl 代表了不同的解决方案。本文将深入对比它们的技术细节,帮助你在架构决策中避开常见陷阱。

🛡️ 环境兼容性:原生绑定 vs 纯 JavaScript

这是前端开发者最需要关注的第一道门槛。浏览器环境不支持 Node.js 的原生模块。

bcrypt 依赖 Node.js 原生 C++ 扩展。

  • 必须通过 node-gyp 编译。
  • 无法直接打包进浏览器代码。
// bcrypt: 仅适用于 Node.js 服务端
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash('my_password', 10);
// 在浏览器中运行此代码会报错:Module not found

crypto-js 是纯 JavaScript 实现。

  • 无需编译,直接可在浏览器运行。
  • 通过 CommonJS 或全局变量使用。
// crypto-js: 浏览器兼容
const CryptoJS = require("crypto-js");
const hash = CryptoJS.SHA256("message").toString();
// 可在 webpack 或浏览器脚本标签中直接加载

node-forge 是纯 JavaScript 实现。

  • 专为跨环境设计(Node 和 浏览器)。
  • 提供浏览器构建版本。
// node-forge: 浏览器兼容
const forge = require('node-forge');
const md = forge.md.sha256.create();
md.update('message');
const hash = md.digest().toHex();
// 支持通过 CDN 引入浏览器

sjcl 是纯 JavaScript 实现。

  • 设计之初就考虑了浏览器环境。
  • 使用位数组处理数据。
// sjcl: 浏览器兼容
const sjcl = require('sjcl');
const hash = sjcl.hash.sha256.hash("message");
const hex = sjcl.codec.hex.fromBits(hash);
// 需处理位数组与字符串的转换

🔐 密码哈希:专用工具 vs 通用加密

密码哈希需要抗暴力破解,与普通加密不同。

bcrypt 是密码哈希的行业标准。

  • 内置盐值生成和成本因子。
  • 自动处理安全细节。
// bcrypt: 专业密码哈希
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash('password', salt);
const match = await bcrypt.compare('password', hash);

crypto-js 提供哈希函数但非专为密码设计。

  • 需要手动管理盐值。
  • 速度过快,不适合直接存储密码。
// crypto-js: 手动哈希
const salt = CryptoJS.lib.WordArray.random(128/8);
const hash = CryptoJS.PBKDF2("password", salt, { keySize: 8 });
// 需自行实现比较逻辑和盐值存储

node-forge 支持 PBKDF2 但配置复杂。

  • 功能强大但代码量大。
  • 适合需要精细控制的场景。
// node-forge: 手动哈希
const salt = forge.random.getBytesSync(16);
const key = forge.pkcs5.pbkdf2('password', salt, 1000, 16);
// 需自行管理迭代次数和衍生密钥长度

sjcl 提供 PBKDF2 实现。

  • 安全系数高。
  • API 较为学术化。
// sjcl: 手动哈希
const salt = sjcl.random.randomWords(4);
const key = sjcl.misc.pbkdf2('password', salt, 1000);
// 返回位数组,需转换才能存储

🔑 对称加密:AES 实现对比

当需要加密敏感数据(如本地存储)时,AES 是常用选择。

bcrypt 不支持对称加密。

  • 仅专注于哈希。
  • 尝试使用会报错。
// bcrypt: 不支持加密
// 无相关 API,不可用于数据加密

crypto-js 提供简易 AES 接口。

  • API 非常直观。
  • 默认模式需注意安全性。
// crypto-js: 简易 AES
const encrypted = CryptoJS.AES.encrypt('message', 'secret key').toString();
const decrypted = CryptoJS.AES.decrypt(encrypted, 'secret key').toString(CryptoJS.enc.Utf8);

node-forge 提供底层 AES 控制。

  • 可指定模式(CBC, GCM 等)。
  • 需手动处理初始化向量 (IV)。
// node-forge: 底层 AES
const cipher = forge.cipher.createCipher('AES-CBC', key);
cipher.start({ iv: iv });
cipher.update(forge.util.createBuffer('message'));
cipher.finish();
const encrypted = cipher.output.toHex();

sjcl 提供安全默认的 AES。

  • 默认使用 CC 模式(类似 GCM)。
  • 自动处理认证标签。
// sjcl: 安全 AES
const encrypted = sjcl.encrypt('password', 'message');
const decrypted = sjcl.decrypt('password', encrypted);
// 返回 JSON 字符串,包含密文和元数据

📜 PKI 与证书:唯一的专业选手

处理 X.509 证书、CSR 或 TLS 握手是特定需求。

bcrypt 完全不支持。

  • 无相关功能。
// bcrypt: 不支持 PKI
// 无相关 API

crypto-js 不支持 PKI。

  • 仅基础加密算法。
// crypto-js: 不支持 PKI
// 无相关 API

node-forge 是 JavaScript 中 PKI 事实标准。

  • 可生成密钥对、CSR、解析证书。
  • 唯一能在此领域替代 OpenSSL 的 JS 库。
// node-forge: 生成 CSR
const keys = forge.pki.rsa.generateKeyPair(2048);
const csr = forge.pki.createCertificationRequest();
csr.publicKey = keys.publicKey;
// 可构建完整的证书请求对象

sjcl 不支持 PKI。

  • 专注于对称加密和哈希。
// sjcl: 不支持 PKI
// 无相关 API

⚠️ 安全警告与现代替代方案

在前端使用加密库存在固有風險。密钥存储在客户端代码中是不安全的。

主要风险

  • 密钥泄露:浏览器中的 JavaScript 代码可被用户查看。
  • 性能阻塞:复杂加密会阻塞主线程,影响 UI 响应。
  • 随机数生成:部分库的随机数生成器不如浏览器原生安全。

现代替代方案:Web Crypto API

对于新项目,优先考虑浏览器原生的 SubtleCrypto。

  • 性能更好(硬件加速)。
  • 更安全(密钥不可导出)。
  • 无需额外依赖。
// Web Crypto API: 原生哈希
const encoder = new TextEncoder();
const data = encoder.encode('message');
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
// 无需安装任何 npm 包

📊 总结对比表

特性bcryptcrypto-jsnode-forgesjcl
运行环境❌ 仅 Node.js✅ 浏览器 + Node✅ 浏览器 + Node✅ 浏览器 + Node
密码哈希✅ 专业支持⚠️ 需手动配置⚠️ 需手动配置⚠️ 需手动配置
对称加密❌ 不支持✅ 简易 API✅ 底层控制✅ 安全默认
PKI/证书❌ 不支持❌ 不支持✅ 完整支持❌ 不支持
维护状态✅ 活跃⚠️ 缓慢✅ 稳定⚠️ 缓慢
** bundle 体积**中 (需编译)小大中

💡 架构师建议

bcrypt 是服务端密码存储的金标准 — 但请把它留在服务器上。前端永远不要尝试用它来哈希密码用于认证传输,这不能替代 HTTPS。

crypto-js 适合内部工具或非敏感数据的简单混淆。如果涉及用户隐私或金融数据,请避免使用。

node-forge 是前端处理证书的唯一可行选择 — 例如在浏览器中生成客户端证书或解密 PKCS#7 数据。虽然体积大,但无可替代。

sjcl 是一个优秀的库,但生态已逐渐转向 Web Crypto API。除非维护旧系统,否则不建议新项目引入。

最终建议:对于大多数前端加密需求,优先使用 Web Crypto API。只有在需要特定功能(如 PKI)或兼容旧浏览器时,再考虑 node-forge。永远记住 — 前端加密无法保护密钥本身,它只能保护数据传输过程中的完整性或实现特定的零知识架构。

如何选择: bcrypt vs crypto-js vs node-forge vs sjcl

  • bcrypt:

    选择 bcrypt 仅限 Node.js 服务端环境用于密码哈希。它依赖原生 C++ 绑定,无法直接在浏览器中运行。如果你的架构需要前端参与密码哈希(如零知识证明),请改用纯 JavaScript 实现如 bcryptjs 或 Web Crypto API。

  • crypto-js:

    选择 crypto-js 仅用于快速原型开发或非敏感数据的简单加密。它的 API 简单易用,但性能不如原生 API,且维护频率较低。对于生产环境的高安全需求,建议优先考虑 Web Crypto API。

  • node-forge:

    选择 node-forge 当你需要在浏览器或 Node.js 中处理复杂的 PKI 任务,如生成 CSR、解析 X.509 证书或实现 TLS 握手。它是纯 JavaScript 实现,兼容性最好,但体积较大。

  • sjcl:

    选择 sjcl 仅当维护旧项目或有特定的学术安全需求。它的位数组操作非常精细,但现代开发中已较少使用。新项目建议评估 node-forge 或 Web Crypto API 以获得更好的长期支持。

bcrypt的README

node.bcrypt.js

ci

Build Status

A library to help you hash passwords.

You can read about bcrypt in Wikipedia as well as in the following article: How To Safely Store A Password

If You Are Submitting Bugs or Issues

Please verify that the NodeJS version you are using is a stable version; Unstable versions are currently not supported and issues created while using an unstable version will be closed.

If you are on a stable version of NodeJS, please provide a sufficient code snippet or log files for installation issues. The code snippet does not require you to include confidential information. However, it must provide enough information so the problem can be replicable, or it may be closed without an explanation.

Version Compatibility

Please upgrade to atleast v5.0.0 to avoid security issues mentioned below.

Node VersionBcrypt Version
0.4<= 0.4
0.6, 0.8, 0.10>= 0.5
0.11>= 0.8
4<= 2.1.0
8>= 1.0.3 < 4.0.0
10, 11>= 3
12 onwards>= 3.0.6

node-gyp only works with stable/released versions of node. Since the bcrypt module uses node-gyp to build and install, you'll need a stable version of node to use bcrypt. If you do not, you'll likely see an error that starts with:

gyp ERR! stack Error: "pre" versions of node cannot be installed, use the --nodedir flag instead

Security Issues And Concerns

Per bcrypt implementation, only the first 72 bytes of a string are used. Any extra bytes are ignored when matching passwords. Note that this is not the first 72 characters. It is possible for a string to contain less than 72 characters, while taking up more than 72 bytes (e.g. a UTF-8 encoded string containing emojis). If a string is provided, it will be encoded using UTF-8.

As should be the case with any security tool, anyone using this library should scrutinise it. If you find or suspect an issue with the code, please bring it to the maintainers' attention. We will spend some time ensuring that this library is as secure as possible.

Here is a list of BCrypt-related security issues/concerns that have come up over the years.

  • An issue with passwords was found with a version of the Blowfish algorithm developed for John the Ripper. This is not present in the OpenBSD version and is thus not a problem for this module. HT zooko.
  • Versions < 5.0.0 suffer from bcrypt wrap-around bug and will truncate passwords >= 255 characters leading to severely weakened passwords. Please upgrade at earliest. See this wiki page for more details.
  • Versions < 5.0.0 do not handle NUL characters inside passwords properly leading to all subsequent characters being dropped and thus resulting in severely weakened passwords. Please upgrade at earliest. See this wiki page for more details.

Compatibility Note

This library supports $2a$ and $2b$ prefix bcrypt hashes. $2x$ and $2y$ hashes are specific to bcrypt implementation developed for John the Ripper. In theory, they should be compatible with $2b$ prefix.

Compatibility with hashes generated by other languages is not 100% guaranteed due to difference in character encodings. However, it should not be an issue for most cases.

Migrating from v1.0.x

Hashes generated in earlier version of bcrypt remain 100% supported in v2.x.x and later versions. In most cases, the migration should be a bump in the package.json.

Hashes generated in v2.x.x using the defaults parameters will not work in earlier versions.

Dependencies

  • NodeJS
  • node-gyp
  • Please check the dependencies for this tool at: https://github.com/nodejs/node-gyp
  • Windows users will need the options for c# and c++ installed with their visual studio instance.
  • Python 2.x/3.x
  • OpenSSL - This is only required to build the bcrypt project if you are using versions <= 0.7.7. Otherwise, we're using the builtin node crypto bindings for seed data (which use the same OpenSSL code paths we were, but don't have the external dependency).

Install via NPM

npm install bcrypt

Note: OS X users using Xcode 4.3.1 or above may need to run the following command in their terminal prior to installing if errors occur regarding xcodebuild: sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer

Pre-built binaries for various NodeJS versions are made available on a best-effort basis.

Only the current stable and supported LTS releases are actively tested against.

There may be an interval between the release of the module and the availabilty of the compiled modules.

Currently, we have pre-built binaries that support the following platforms:

  1. Windows x32 and x64
  2. Linux x64 (GlibC and musl)
  3. macOS

If you face an error like this:

node-pre-gyp ERR! Tried to download(404): https://github.com/kelektiv/node.bcrypt.js/releases/download/v1.0.2/bcrypt_lib-v1.0.2-node-v48-linux-x64.tar.gz

make sure you have the appropriate dependencies installed and configured for your platform. You can find installation instructions for the dependencies for some common platforms in this page.

Usage

async (recommended)

const bcrypt = require('bcrypt');
const saltRounds = 10;
const myPlaintextPassword = 's0/\/\P4$$w0rD';
const someOtherPlaintextPassword = 'not_bacon';

To hash a password:

Technique 1 (generate a salt and hash on separate function calls):

bcrypt.genSalt(saltRounds, function(err, salt) {
    bcrypt.hash(myPlaintextPassword, salt, function(err, hash) {
        // Store hash in your password DB.
    });
});

Technique 2 (auto-gen a salt and hash):

bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
    // Store hash in your password DB.
});

Note that both techniques achieve the same end-result.

To check a password:

// Load hash from your password DB.
bcrypt.compare(myPlaintextPassword, hash, function(err, result) {
    // result == true
});
bcrypt.compare(someOtherPlaintextPassword, hash, function(err, result) {
    // result == false
});

A Note on Timing Attacks

with promises

bcrypt uses whatever Promise implementation is available in global.Promise. NodeJS >= 0.12 has a native Promise implementation built in. However, this should work in any Promises/A+ compliant implementation.

Async methods that accept a callback, return a Promise when callback is not specified if Promise support is available.

bcrypt.hash(myPlaintextPassword, saltRounds).then(function(hash) {
    // Store hash in your password DB.
});
// Load hash from your password DB.
bcrypt.compare(myPlaintextPassword, hash).then(function(result) {
    // result == true
});
bcrypt.compare(someOtherPlaintextPassword, hash).then(function(result) {
    // result == false
});

This is also compatible with async/await

async function checkUser(username, password) {
    //... fetch user from a db etc.

    const match = await bcrypt.compare(password, user.passwordHash);

    if(match) {
        //login
    }

    //...
}

ESM import

import bcrypt from "bcrypt";

// later
await bcrypt.compare(password, hash);

sync

const bcrypt = require('bcrypt');
const saltRounds = 10;
const myPlaintextPassword = 's0/\/\P4$$w0rD';
const someOtherPlaintextPassword = 'not_bacon';

To hash a password:

Technique 1 (generate a salt and hash on separate function calls):

const salt = bcrypt.genSaltSync(saltRounds);
const hash = bcrypt.hashSync(myPlaintextPassword, salt);
// Store hash in your password DB.

Technique 2 (auto-gen a salt and hash):

const hash = bcrypt.hashSync(myPlaintextPassword, saltRounds);
// Store hash in your password DB.

As with async, both techniques achieve the same end-result.

To check a password:

// Load hash from your password DB.
bcrypt.compareSync(myPlaintextPassword, hash); // true
bcrypt.compareSync(someOtherPlaintextPassword, hash); // false

A Note on Timing Attacks

Why is async mode recommended over sync mode?

We recommend using async API if you use bcrypt on a server. Bcrypt hashing is CPU intensive which will cause the sync APIs to block the event loop and prevent your application from servicing any inbound requests or events. The async version uses a thread pool which does not block the main event loop.

API

BCrypt.

  • genSaltSync(rounds, minor)
    • rounds - [OPTIONAL] - the cost of processing the data. (default - 10)
    • minor - [OPTIONAL] - minor version of bcrypt to use. (default - b)
  • genSalt(rounds, minor, cb)
    • rounds - [OPTIONAL] - the cost of processing the data. (default - 10)
    • minor - [OPTIONAL] - minor version of bcrypt to use. (default - b)
    • cb - [OPTIONAL] - a callback to be fired once the salt has been generated. uses eio making it asynchronous. If cb is not specified, a Promise is returned if Promise support is available.
      • err - First parameter to the callback detailing any errors.
      • salt - Second parameter to the callback providing the generated salt.
  • hashSync(data, salt)
    • data - [REQUIRED] - the data to be encrypted.
    • salt - [REQUIRED] - the salt to be used to hash the password. if specified as a number then a salt will be generated with the specified number of rounds and used (see example under Usage).
  • hash(data, salt, cb)
    • data - [REQUIRED] - the data to be encrypted.
    • salt - [REQUIRED] - the salt to be used to hash the password. if specified as a number then a salt will be generated with the specified number of rounds and used (see example under Usage).
    • cb - [OPTIONAL] - a callback to be fired once the data has been encrypted. uses eio making it asynchronous. If cb is not specified, a Promise is returned if Promise support is available.
      • err - First parameter to the callback detailing any errors.
      • encrypted - Second parameter to the callback providing the encrypted form.
  • compareSync(data, encrypted)
    • data - [REQUIRED] - data to compare.
    • encrypted - [REQUIRED] - data to be compared to.
  • compare(data, encrypted, cb)
    • data - [REQUIRED] - data to compare.
    • encrypted - [REQUIRED] - data to be compared to.
    • cb - [OPTIONAL] - a callback to be fired once the data has been compared. uses eio making it asynchronous. If cb is not specified, a Promise is returned if Promise support is available.
      • err - First parameter to the callback detailing any errors.
      • same - Second parameter to the callback providing whether the data and encrypted forms match [true | false].
  • getRounds(encrypted) - return the number of rounds used to encrypt a given hash
    • encrypted - [REQUIRED] - hash from which the number of rounds used should be extracted.

A Note on Rounds

A note about the cost: when you are hashing your data, the module will go through a series of rounds to give you a secure hash. The value you submit is not just the number of rounds the module will go through to hash your data. The module will use the value you enter and go through 2^rounds hashing iterations.

From @garthk, on a 2GHz core you can roughly expect:

rounds=8 : ~40 hashes/sec
rounds=9 : ~20 hashes/sec
rounds=10: ~10 hashes/sec
rounds=11: ~5  hashes/sec
rounds=12: 2-3 hashes/sec
rounds=13: ~1 sec/hash
rounds=14: ~1.5 sec/hash
rounds=15: ~3 sec/hash
rounds=25: ~1 hour/hash
rounds=31: 2-3 days/hash

A Note on Timing Attacks

Because it's come up multiple times in this project and other bcrypt projects, it needs to be said. The bcrypt library is not susceptible to timing attacks. From codahale/bcrypt-ruby#42:

One of the desired properties of a cryptographic hash function is preimage attack resistance, which means there is no shortcut for generating a message which, when hashed, produces a specific digest.

A great thread on this, in much more detail can be found @ codahale/bcrypt-ruby#43

If you're unfamiliar with timing attacks and want to learn more you can find a great writeup @ A Lesson In Timing Attacks

However, timing attacks are real. And the comparison function is not time safe. That means that it may exit the function early in the comparison process. Timing attacks happen because of the above. We don't need to be careful that an attacker will learn anything, and our comparison function provides a comparison of hashes. It is a utility to the overall purpose of the library. If you end up using it for something else, we cannot guarantee the security of the comparator. Keep that in mind as you use the library.

Hash Info

The characters that comprise the resultant hash are ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$.

Resultant hashes will be 60 characters long and they will include the salt among other parameters, as follows:

$[algorithm]$[cost]$[salt][hash]

  • 2 chars hash algorithm identifier prefix. "$2a$" or "$2b$" indicates BCrypt
  • Cost-factor (n). Represents the exponent used to determine how many iterations 2^n
  • 16-byte (128-bit) salt, base64 encoded to 22 characters
  • 24-byte (192-bit) hash, base64 encoded to 31 characters

Example:

$2b$10$nOUIs5kJ7naTuTFkBy1veuK0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
 |  |  |                     |
 |  |  |                     hash-value = K0kSxUFXfuaOKdOKf9xYT0KKIGSJwFa
 |  |  |
 |  |  salt = nOUIs5kJ7naTuTFkBy1veu
 |  |
 |  cost-factor => 10 = 2^10 rounds
 |
 hash-algorithm identifier => 2b = BCrypt

Testing

If you create a pull request, tests better pass :)

npm install
npm test

Credits

The code for this comes from a few sources:

Contributors

License

Unless stated elsewhere, file headers or otherwise, the license as stated in the LICENSE file.