bcryptjs, crypto-js, hash.js, and sha.js are JavaScript libraries used for hashing and encryption, but they serve distinct security purposes. bcryptjs is a pure JavaScript implementation of the bcrypt password hashing algorithm, designed specifically for securely storing passwords with built-in salting and cost factors. crypto-js is a broad toolkit offering various algorithms (MD5, SHA, AES, HMAC) for general-purpose hashing and encryption in the browser. hash.js provides a lightweight, modular collection of hash functions (SHA, MD5, BLAKE2) optimized for speed and small bundle sizes. sha.js is a focused, streamable implementation of the SHA family (SHA-1, SHA-256, etc.) often used as a dependency for other crypto tools. Understanding their specific strengths is critical for avoiding security anti-patterns like using fast hashes for passwords.
When securing web applications, picking the right cryptographic tool is not just about functionalityβit's about understanding the threat model. bcryptjs, crypto-js, hash.js, and sha.js all handle cryptographic operations, but they solve very different problems. Using the wrong one can lead to severe security vulnerabilities, such as storing passwords with fast hashes that hackers can crack in seconds. Let's break down exactly how they differ and when to use each.
The most critical distinction in this group is between password hashing and general-purpose hashing. Passwords require slow, adaptive algorithms to prevent brute-force attacks. General hashes are designed to be fast for data integrity.
bcryptjs is the only library here designed for passwords. It implements the bcrypt algorithm, which includes a random salt and a configurable "cost" factor to slow down computation.
// bcryptjs: Securely hashing a password
const bcrypt = require('bcryptjs');
const salt = bcrypt.genSaltSync(10); // Generate a random salt
const hash = bcrypt.hashSync('mySecretPassword', salt);
// Verifying later
const isValid = bcrypt.compareSync('mySecretPassword', hash);
crypto-js, hash.js, and sha.js are fast hashes. If you use them for passwords, you are creating a security risk. They are meant for checking if a file changed, not for protecting secrets.
// crypto-js: WRONG for passwords (Fast hash)
const CryptoJS = require('crypto-js');
// This is instant to compute, making it easy for hackers to guess millions of passwords
const hash = CryptoJS.SHA256('mySecretPassword').toString();
// hash.js: WRONG for passwords (Fast hash)
const hash = require('hash.js');
// Also instant; vulnerable to rainbow tables without complex manual salting
const result = hash.sha256().update('mySecretPassword').digest('hex');
// sha.js: WRONG for passwords (Fast hash)
const SHA256 = require('sha.js/sha256');
// Same vulnerability; designed for speed, not password protection
const hash = new SHA256().update('mySecretPassword').digest('hex');
Libraries differ in how many algorithms they provide. Some are Swiss Army knives; others are precision screwdrivers.
crypto-js is a massive collection. It includes MD5, SHA-1, SHA-256, AES, Rabbit, TripleDES, and HMAC. You import one package and get everything.
// crypto-js: Accessing multiple algorithms from one package
const CryptoJS = require('crypto-js');
const sha256 = CryptoJS.SHA256('message');
const md5 = CryptoJS.MD5('message');
const encrypted = CryptoJS.AES.encrypt('message', 'secret-key');
hash.js is modular. It supports SHA, MD5, BLAKE2, and HMAC, but you typically import only what you need to keep your bundle small.
// hash.js: Importing specific algorithms
const hash = require('hash.js');
const sha256 = hash.sha256().update('message').digest('hex');
const blake2b = hash.blake2b().update('message').digest('hex');
sha.js is hyper-focused. It only does the SHA family (SHA-1, SHA-224, SHA-256, SHA-384, SHA-512). It doesn't do AES or MD5.
// sha.js: Focused strictly on SHA variants
const SHA512 = require('sha.js/sha512');
const hash = new SHA512().update('message').digest('hex');
bcryptjs is single-purpose. It only does bcrypt. You cannot use it to encrypt a message or hash a file for integrity.
// bcryptjs: Single purpose only
const bcrypt = require('bcryptjs');
// No methods for AES or SHA exist here
const hash = bcrypt.hashSync('data', 10);
When hashing large files (like video uploads) in the browser or Node.js, you cannot load the whole file into memory. You need streams. Not all libraries handle this well.
sha.js excels here. It was built to mimic the Node.js crypto stream API, making it perfect for piping large data chunks.
// sha.js: Streaming a large file
const SHA256 = require('sha.js/sha256');
const fs = require('fs');
const hash = new SHA256();
const stream = fs.createReadStream('large-video.mp4');
stream.on('data', (chunk) => hash.update(chunk));
stream.on('end', () => {
console.log(hash.digest('hex'));
});
hash.js also supports updating in chunks, which works well for manual streaming logic.
// hash.js: Manual chunk updates
const hash = require('hash.js').sha256();
hash.update('chunk1');
hash.update('chunk2');
const result = hash.digest('hex');
crypto-js generally expects the full message at once. While it has some word-array handling, it is not designed for Node-style streams and can be memory-intensive for very large files.
// crypto-js: Typically loads full content
const CryptoJS = require('crypto-js');
// You must have the whole string or word array ready
const hash = CryptoJS.SHA256('entire large content string');
bcryptjs does not support streaming. It processes the entire password string in memory, which is fine for passwords (short text) but impossible for files.
// bcryptjs: No streaming support
const bcrypt = require('bcryptjs');
// Must pass the full string
const hash = bcrypt.hashSync('shortPassword', 10);
How these libraries fit into your build pipeline matters for long-term maintenance.
sha.js is often a dependency of other tools (like browserify-sign or create-hash). It is low-level and mimics the Node.js crypto module API closely. It is a building block rather than a finished product for most app developers.
// sha.js: Often used as a polyfill or dependency
// Common in bundler configs to replace Node's 'crypto' module
// Direct usage looks like Node's native API
const createHash = require('sha.js/sha256'); // Conceptual usage
hash.js is standalone and written in pure JavaScript with no dependencies. It is highly optimized for size, making it a favorite for frontend bundles where every kilobyte counts.
// hash.js: Zero dependencies, easy to bundle
const hash = require('hash.js');
// Works immediately in browser or Node without setup
crypto-js is also standalone but larger. It is self-contained and easy to drop into a project via CDN or npm, but you pay for the convenience with a larger file size if you don't tree-shake effectively.
// crypto-js: Large standalone bundle
const CryptoJS = require('crypto-js');
// Includes many algorithms you might not use
bcryptjs is a pure JS reimplementation of the C++ bcrypt library. It is used when native modules are unavailable (like in browsers). It is heavier than a simple hash function because of the computational work it must perform.
// bcryptjs: Pure JS implementation for compatibility
const bcrypt = require('bcryptjs');
// Runs everywhere, but slower than native C++ bcrypt
Security is not just about picking a library; it's about using it correctly. Here is how the community recommends using each.
bcryptjs (or native bcrypt in Node.js).sha.js, hash.js, crypto-js (SHA/MD5).sha.js or hash.js (SHA-256 or SHA-512).crypto-js (if bundle size matters), bcryptjs.crypto-js (AES).bcryptjs, hash.js, sha.js.crypto-js in this list provides symmetric encryption (AES) out of the box for hiding data content. Note: Client-side encryption key management is extremely difficult and often discouraged unless you have specific expertise.| Feature | bcryptjs | crypto-js | hash.js | sha.js |
|---|---|---|---|---|
| Primary Use | Password Hashing | General Crypto (AES, Hash) | Fast Hashing | SHA Streaming |
| Password Safe? | β Yes (Adaptive) | β No (Fast) | β No (Fast) | β No (Fast) |
| Encryption (AES) | β No | β Yes | β No | β No |
| Streaming | β No | β οΈ Limited | β Yes (Chunked) | β Yes (Streams) |
| Bundle Size | Medium | Large | Small | Small/Medium |
| Algorithms | Bcrypt only | MD5, SHA, AES, HMAC | SHA, MD5, BLAKE2 | SHA Family only |
If you are building a login system, bcryptjs is your only choice among these for hashing passwords. Never try to build your own password security using SHA-256 from sha.js or hash.js; you will expose your users to risk.
If you need to verify that a downloaded file hasn't been tampered with, hash.js or sha.js are excellent, lightweight choices. Pick sha.js if you are dealing with massive files and need stream compatibility; pick hash.js if you want the smallest possible footprint for standard hashes.
If you need to encrypt data (not just hash it) or need a quick-and-dirty implementation of many algorithms for a prototype, crypto-js provides the broadest toolkit, but be mindful of the bundle size cost.
Final Thought: Cryptography is unforgiving. Using a fast tool for a slow job (passwords) is a critical failure. Match the tool to the threat: use bcryptjs for secrets humans type, and the others for data machines process.
Choose bcryptjs exclusively for hashing user passwords in environments where you cannot use the native Node.js bcrypt module (e.g., pure browser environments or serverless runtimes without native bindings). It is the only correct choice among these four for password storage because it implements a slow, adaptive hashing algorithm that resists brute-force attacks. Do not use it for general file hashing or data integrity checks due to its intentional slowness.
Choose crypto-js when you need a comprehensive, all-in-one toolkit for client-side encryption, decryption, and varied hashing algorithms without managing multiple dependencies. It is ideal for quick prototypes or applications requiring AES encryption, HMAC generation, or legacy hashes like MD5 directly in the browser. Avoid it for high-performance streaming of large files or as a primary dependency for modern password hashing, as it lacks native streaming support for some operations and is not optimized for minimal bundle size.
Choose hash.js when you need a lightweight, modular, and fast implementation of standard hash functions (like SHA-256 or BLAKE2b) for data integrity checks, checksums, or blockchain-related tasks. It is an excellent choice for frontend projects where bundle size is a concern, as it allows tree-shaking of specific algorithms. Do not use it for password storage, as these are fast cryptographic hashes vulnerable to rainbow table and brute-force attacks if used without a proper key derivation function.
Choose sha.js if you require a streamable, low-level implementation of the SHA family that integrates well with Node.js streams or browser blob streaming for processing large datasets. It is often selected as a foundational dependency for other cryptographic libraries rather than for direct application logic. Avoid using it directly for simple string hashing if a higher-level library like crypto-js or hash.js offers a more convenient API, and never use it alone for password storage.
Optimized bcrypt in JavaScript with zero dependencies, with TypeScript support. Compatible to the C++ bcrypt binding on Node.js and also working in the browser.
Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power. (see)
While bcrypt.js is compatible to the C++ bcrypt binding, it is written in pure JavaScript and thus slower (about 30%), effectively reducing the number of iterations that can be processed in an equal time span.
The maximum input length is 72 bytes (note that UTF-8 encoded characters use up to 4 bytes) and the length of generated
hashes is 60 characters. Note that maximum input length is not implicitly checked by the library for compatibility with
the C++ binding on Node.js, but should be checked with bcrypt.truncates(password) where necessary.
The package exports an ECMAScript module with an UMD fallback.
$> npm install bcryptjs
import bcrypt from "bcryptjs";
https://cdn.jsdelivr.net/gh/dcodeIO/bcrypt.js@TAG/index.js (ESM)https://cdn.jsdelivr.net/npm/bcryptjs@VERSION/index.js (ESM)https://cdn.jsdelivr.net/npm/bcryptjs@VERSION/umd/index.js (UMD)https://unpkg.com/bcryptjs@VERSION/index.js (ESM)https://unpkg.com/bcryptjs@VERSION/umd/index.js (UMD)Replace TAG respectively VERSION with a specific version or omit it (not recommended in production) to use latest.
When using the ESM variant in a browser, the crypto import needs to be stubbed out, for example using an import map. Bundlers should omit it automatically.
To hash a password:
const salt = bcrypt.genSaltSync(10);
const hash = bcrypt.hashSync("B4c0/\/", salt);
// Store hash in your password DB
To check a password:
// Load hash from your password DB
bcrypt.compareSync("B4c0/\/", hash); // true
bcrypt.compareSync("not_bacon", hash); // false
Auto-gen a salt and hash:
const hash = bcrypt.hashSync("bacon", 10);
To hash a password:
const salt = await bcrypt.genSalt(10);
const hash = await bcrypt.hash("B4c0/\/", salt);
// Store hash in your password DB
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash("B4c0/\/", salt, function (err, hash) {
// Store hash in your password DB
});
});
To check a password:
// Load hash from your password DB
await bcrypt.compare("B4c0/\/", hash); // true
await bcrypt.compare("not_bacon", hash); // false
// Load hash from your password DB
bcrypt.compare("B4c0/\/", hash, (err, res) => {
// res === true
});
bcrypt.compare("not_bacon", hash, (err, res) => {
// res === false
});
Auto-gen a salt and hash:
await bcrypt.hash("B4c0/\/", 10);
// Store hash in your password DB
bcrypt.hash("B4c0/\/", 10, (err, hash) => {
// Store hash in your password DB
});
Note: Under the hood, asynchronous APIs split an operation into small chunks. After the completion of a chunk, the execution of the next chunk is placed on the back of the JS event queue, efficiently yielding for other computation to execute.
Usage: bcrypt <input> [rounds|salt]
Callback<T>: (err: Error | null, result?: T) => void
Called with an error on failure or a value of type T upon success.
ProgressCallback: (percentage: number) => void
Called with the percentage of rounds completed (0.0 - 1.0), maximally once per MAX_EXECUTION_TIME = 100 ms.
RandomFallback: (length: number) => number[]
Called to obtain random bytes when both Web Crypto API and Node.js
crypto are not available.
bcrypt.genSaltSync(rounds?: number): string
Synchronously generates a salt. Number of rounds defaults to 10 when omitted.
bcrypt.genSalt(rounds?: number): Promise<string>
Asynchronously generates a salt. Number of rounds defaults to 10 when omitted.
bcrypt.genSalt([rounds: number, ]callback: Callback<string>): void
Asynchronously generates a salt. Number of rounds defaults to 10 when omitted.
bcrypt.truncates(password: string): boolean
Tests if a password will be truncated when hashed, that is its length is greater than 72 bytes when converted to UTF-8.
bcrypt.hashSync(password: string, salt?: number | string): string
Synchronously generates a hash for the given password. Number of rounds defaults to 10 when omitted.
bcrypt.hash(password: string, salt: number | string): Promise<string>
Asynchronously generates a hash for the given password.
bcrypt.hash(password: string, salt: number | string, callback: Callback<string>, progressCallback?: ProgressCallback): void
Asynchronously generates a hash for the given password.
bcrypt.compareSync(password: string, hash: string): boolean
Synchronously tests a password against a hash.
bcrypt.compare(password: string, hash: string): Promise<boolean>
Asynchronously compares a password against a hash.
bcrypt.compare(password: string, hash: string, callback: Callback<boolean>, progressCallback?: ProgressCallback)
Asynchronously compares a password against a hash.
bcrypt.getRounds(hash: string): number
Gets the number of rounds used to encrypt the specified hash.
bcrypt.getSalt(hash: string): string
Gets the salt portion from a hash. Does not validate the hash.
bcrypt.setRandomFallback(random: RandomFallback): void
Sets the pseudo random number generator to use as a fallback if neither Web Crypto API nor Node.js crypto are available. Please note: It is highly important that the PRNG used is cryptographically secure and that it is seeded properly!
Building the UMD fallback:
$> npm run build
Running the tests:
$> npm test
Based on work started by Shane Girish at bcrypt-nodejs, which is itself based on javascript-bcrypt (New BSD-licensed).