What is hash-wasm?
The hash-wasm npm package provides high-performance hashing algorithms implemented in WebAssembly. It supports a variety of hash functions and is designed to be fast and efficient, making it suitable for both client-side and server-side applications.
What are hash-wasm's main functionalities?
SHA-256 Hashing
This feature allows you to compute the SHA-256 hash of a given input string. The code sample demonstrates how to use the sha256 function from the hash-wasm package to hash the string 'Hello, world!'.
const { sha256 } = require('hash-wasm');
(async () => {
const hash = await sha256('Hello, world!');
console.log(hash); // Outputs the SHA-256 hash of the input string
})();
MD5 Hashing
This feature allows you to compute the MD5 hash of a given input string. The code sample demonstrates how to use the md5 function from the hash-wasm package to hash the string 'Hello, world!'.
const { md5 } = require('hash-wasm');
(async () => {
const hash = await md5('Hello, world!');
console.log(hash); // Outputs the MD5 hash of the input string
})();
Blake2b Hashing
This feature allows you to compute the Blake2b hash of a given input string. The code sample demonstrates how to use the blake2b function from the hash-wasm package to hash the string 'Hello, world!'.
const { blake2b } = require('hash-wasm');
(async () => {
const hash = await blake2b('Hello, world!');
console.log(hash); // Outputs the Blake2b hash of the input string
})();
Other packages similar to hash-wasm
crypto-js
Crypto-js is a widely-used library that provides a variety of cryptographic algorithms including hashing, encryption, and decryption. It is written in JavaScript and is suitable for both client-side and server-side applications. Compared to hash-wasm, crypto-js is more versatile but may not be as performant due to its pure JavaScript implementation.
node-forge
Node-forge is a comprehensive library for implementing cryptographic functions in JavaScript. It supports a wide range of algorithms and is designed to work in both Node.js and browser environments. While node-forge offers extensive functionality, it may not match the performance of hash-wasm's WebAssembly-based implementation.
js-sha256
Js-sha256 is a lightweight library focused specifically on SHA-256 hashing. It is easy to use and performs well for its specific purpose. However, it lacks the variety of hashing algorithms provided by hash-wasm and may not be as fast due to its JavaScript implementation.
hash-wasm

Hash-WASM is a ⚡lightning fast⚡ hash function library for browsers and Node.js.
It is using hand-tuned WebAssembly binaries to calculate the hash faster than other libraries.
Supported algorithms
Adler-32 | 3 kB |
Argon2: Argon2d, Argon2i, Argon2id (v1.3) | 11 kB |
bcrypt | 11 kB |
BLAKE2b | 6 kB |
BLAKE2s | 5 kB |
BLAKE3 | 9 kB |
CRC32 | 3 kB |
CRC64 | 4 kB |
HMAC | - |
MD4 | 4 kB |
MD5 | 4 kB |
PBKDF2 | - |
RIPEMD-160 | 5 kB |
scrypt | 10 kB |
SHA-1 | 5 kB |
SHA-2: SHA-224, SHA-256 | 7 kB |
SHA-2: SHA-384, SHA-512 | 8 kB |
SHA-3: SHA3-224, SHA3-256, SHA3-384, SHA3-512 | 4 kB |
Keccak-224, Keccak-256, Keccak-384, Keccak-512 | 4 kB |
SM3 | 4 kB |
Whirlpool | 6 kB |
xxHash32 | 3 kB |
xxHash64 | 4 kB |
xxHash3 | 7 kB |
xxHash128 | 8 kB |
Features
- A lot faster than other JS / WASM implementations (see benchmarks below)
- It's lightweight. See the table above
- Compiled from heavily optimized algorithms written in C
- Supports all modern browsers, Node.js and Deno
- Supports large data streams
- Supports UTF-8 strings and typed arrays
- Supports chunked input streams
- Modular architecture (the algorithms are compiled into individual WASM binaries)
- WASM modules are bundled as base64 strings (no problems with linking)
- Supports tree shaking (Webpack only bundles the hash algorithms you use)
- Works without Webpack or other bundlers
- Includes TypeScript type definitions
- Works in Web Workers
- Zero dependencies
- Supports concurrent hash calculations with multiple states
- Supports saving and loading the internal state of the hash (segmented hashing and rewinding)
- Unit tests for all algorithms
- 100% open source & transparent build process
- Easy to use, Promise-based API
Installation
npm i hash-wasm
It can also be used directly from HTML (via jsDelivr):
<script src="https://cdn.jsdelivr.net/npm/hash-wasm@4"></script>
<script src="https://cdn.jsdelivr.net/npm/hash-wasm@4/dist/md5.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hash-wasm@4/dist/hmac.umd.min.js"></script>
Examples
Demo apps
Hash calculator - source code
MD5 file hasher using HTML5 File API
Usage with the shorthand form
It is the easiest and the fastest way to calculate hashes. Use it when the input buffer is already in the memory.
import { md5, sha1, sha512, sha3 } from "hash-wasm";
async function run() {
console.log("MD5:", await md5("demo"));
const int8Buffer = new Uint8Array([0, 1, 2, 3]);
console.log("SHA1:", await sha1(int8Buffer));
console.log("SHA512:", await sha512(int8Buffer));
const int32Buffer = new Uint32Array([1056, 641]);
console.log("SHA3-256:", await sha3(int32Buffer, 256));
}
run();
* See String encoding pitfalls
** See API reference
Advanced usage with streaming input
createXXXX() functions create new WASM instances with separate states, which can be used to calculate multiple hashes paralelly. They are slower compared to shorthand functions like md5(), which reuse the same WASM instance and state to do multiple calculations. For this reason, the shorthand form is always preferred when the data is already in the memory.
For the best performance, avoid calling createXXXX() functions in loops. When calculating multiple hashes sequentially, the init() function can be used to reset the internal state between runs. It is faster than creating new instances with createXXXX().
import { createSHA1 } from "hash-wasm";
async function run() {
const sha1 = await createSHA1();
sha1.init();
while (hasMoreData()) {
const chunk = readChunk();
sha1.update(chunk);
}
const hash = sha1.digest("binary");
console.log("SHA1:", hash);
}
run();
* See String encoding pitfalls
** See API reference
Hashing passwords with Argon2
The recommended process for choosing the parameters can be found here: https://tools.ietf.org/html/draft-irtf-cfrg-argon2-04#section-4
import { argon2id, argon2Verify } from "hash-wasm";
async function run() {
const salt = new Uint8Array(16);
window.crypto.getRandomValues(salt);
const key = await argon2id({
password: "pass",
salt,
parallelism: 1,
iterations: 256,
memorySize: 512,
hashLength: 32,
outputType: "encoded",
});
console.log("Derived key:", key);
const isValid = await argon2Verify({
password: "pass",
hash: key,
});
console.log(isValid ? "Valid password" : "Invalid password");
}
run();
* See String encoding pitfalls
** See API reference
Hashing passwords with bcrypt
import { bcrypt, bcryptVerify } from "hash-wasm";
async function run() {
const salt = new Uint8Array(16);
window.crypto.getRandomValues(salt);
const key = await bcrypt({
password: "pass",
salt,
costFactor: 11,
outputType: "encoded",
});
console.log("Derived key:", key);
const isValid = await bcryptVerify({
password: "pass",
hash: key,
});
console.log(isValid ? "Valid password" : "Invalid password");
}
run();
* See String encoding pitfalls
** See API reference
Calculating HMAC
All supported hash functions can be used to calculate HMAC. For the best performance, avoid calling createXXXX() in loops (see Advanced usage with streaming input
section above)
import { createHMAC, createSHA3 } from "hash-wasm";
async function run() {
const hashFunc = createSHA3(224);
const hmac = await createHMAC(hashFunc, "key");
const fruits = ["apple", "raspberry", "watermelon"];
console.log("Input:", fruits);
const codes = fruits.map((data) => {
hmac.init();
hmac.update(data);
return hmac.digest();
});
console.log("HMAC:", codes);
}
run();
* See String encoding pitfalls
** See API reference
Calculating PBKDF2
All supported hash functions can be used to calculate PBKDF2. For the best performance, avoid calling createXXXX() in loops (see Advanced usage with streaming input
section above)
import { pbkdf2, createSHA1 } from "hash-wasm";
async function run() {
const salt = new Uint8Array(16);
window.crypto.getRandomValues(salt);
const key = await pbkdf2({
password: "password",
salt,
iterations: 1000,
hashLength: 32,
hashFunction: createSHA1(),
outputType: "hex",
});
console.log("Derived key:", key);
}
run();
* See String encoding pitfalls
** See API reference
String encoding pitfalls
You should be aware that there may be multiple UTF-8 representations of a given string:
"\u00fc";
"u\u0308";
"\u00fc" === "u\u0308";
"ü" === "ü";
All algorithms defined in this library depend on the binary representation of the input string. Thus, it's highly recommended to normalize your strings before passing it to hash-wasm. You can use the normalize()
built-in String function to archive this:
"\u00fc".normalize() === "u\u0308".normalize();
const te = new TextEncoder();
te.encode("u\u0308");
te.encode("\u00fc");
te.encode("u\u0308".normalize("NFKC"));
te.encode("\u00fc".normalize("NFKC"));
You can read more about this issue here: https://en.wikipedia.org/wiki/Unicode_equivalence
Resumable hashing
You can save the current internal state of the hash using the .save()
function. This state may be written to disk or stored elsewhere in memory.
You can then use the .load(state)
function to reload that state into a new instance of the hash, or back into the same instance.
This allows you to span the work of hashing a file across multiple processes (e.g. in environments with limited execution times like AWS Lambda, where large jobs need to be split across multiple invocations), or rewind the hash to an earlier point in the stream. For example, the first process could:
const md5 = await createMD5();
md5.init();
md5.update("Hello, ");
const state = md5.save();
const md5 = await createMD5();
md5.load(state);
md5.update("world!");
console.log(md5.digest());
Note that both the saving and loading processes must be running compatible versions of the hash function (i.e. the hash function hasn't changed between the versions of hash-wasm used in the saving and loading processes). If the saved state is incompatible, load()
will throw an exception.
The saved state can contain information about the input, including plaintext input bytes, so from a security perspective it must be treated with the same care as the input data itself.
Browser support
57+ | 11+ | 53+ | 16+ | Not supported | 8+ | 1+ |
Benchmark
You can make your own measurements here: link
Two scenarios were measured:
- throughput with the short form (input size = 32 bytes)
- throughput with the short form (input size = 1MB)
Results:
hash-wasm 4.10.0 | 110.52 MB/s | 850.31 MB/s |
spark-md5 3.0.2 (from npm) | 38.87 MB/s | 171.73 MB/s |
md5-wasm 2.0.0 (from npm) | 37.36 MB/s | 131.77 MB/s |
crypto-js 4.1.1 (from npm) | 9.30 MB/s | 46.71 MB/s |
node-forge 1.3.1 (from npm) | 18.23 MB/s | 28.94 MB/s |
md5 2.3.0 (from npm) | 14.50 MB/s | 21.65 MB/s |
hash-wasm 4.10.0 | 83.80 MB/s | 798.19 MB/s |
jsSHA 3.3.1 (from npm) | 34.93 MB/s | 78.12 MB/s |
crypto-js 4.1.1 (from npm) | 9.50 MB/s | 69.02 MB/s |
node-forge 1.3.1 (from npm) | 17.02 MB/s | 32.00 MB/s |
sha1 1.1.1 (from npm) | 14.68 MB/s | 24.24 MB/s |
hash-wasm 4.10.0 | 63.99 MB/s | 426.16 MB/s |
sha256-wasm 2.2.2 (from npm) | 20.37 MB/s | 308.39 MB/s |
noble-hashes 1.3.2 (from npm) | 24.73 MB/s | 110.02 MB/s |
crypto-js 4.1.1 (from npm) | 8.99 MB/s | 65.17 MB/s |
jsSHA 3.3.1 (from npm) | 25.64 MB/s | 57.98 MB/s |
node-forge 1.3.1 (from npm) | 13.93 MB/s | 28.19 MB/s |
hash-wasm 4.10.0 | 38.06 MB/s | 234.40 MB/s |
sha3-wasm 1.0.0 (from npm) | 15.44 MB/s | 101.51 MB/s |
noble-hashes 1.3.2 (from npm) | 5.74 MB/s | 14.19 MB/s |
sha3 2.1.4 (from npm) | 3.80 MB/s | 10.73 MB/s |
jsSHA 3.2.0 (from npm) | 2.08 MB/s | 3.82 MB/s |
hash-wasm 4.10.0 | 101.66 MB/s | 15 989 MB/s |
xxhash-wasm 1.0.2 (from npm) | 47.58 MB/s | 15 929 MB/s |
xxhashjs 0.2.2 (from npm) | 0.92 MB/s | 42.26 MB/s |
hash-wasm 4.10.0 | 588 ops |
noble-hashes 1.3.2 (from npm) | 395 ops |
pbkdf2 3.1.2 (from npm) | 83 ops |
crypto-js 4.1.1 (from npm) | 29 ops |
hash-wasm 4.10.0 | 438 ops |
argon2-browser 1.18.0 (from npm) | 213 ops |
argon2-wasm-pro 1.1.0 (from npm) | 203 ops |
argon2-wasm 0.9.0 (from npm) | 195 ops |
* These measurements were made with Chrome v131
on a Ryzen 9 7900X desktop CPU.
API
type IDataType = string | Buffer | Uint8Array | Uint16Array | Uint32Array;
adler32(data: IDataType): Promise<string>
blake2b(data: IDataType, bits?: number, key?: IDataType): Promise<string>
blake2s(data: IDataType, bits?: number, key?: IDataType): Promise<string>
blake3(data: IDataType, bits?: number, key?: IDataType): Promise<string>
crc32(data: IDataType, polynomial?: number): Promise<string>
crc64(data: IDataType, polynomial?: string): Promise<string>
keccak(data: IDataType, bits?: 224 | 256 | 384 | 512): Promise<string>
md4(data: IDataType): Promise<string>
md5(data: IDataType): Promise<string>
ripemd160(data: IDataType): Promise<string>
sha1(data: IDataType): Promise<string>
sha224(data: IDataType): Promise<string>
sha256(data: IDataType): Promise<string>
sha3(data: IDataType, bits?: 224 | 256 | 384 | 512): Promise<string>
sha384(data: IDataType): Promise<string>
sha512(data: IDataType): Promise<string>
sm3(data: IDataType): Promise<string>
whirlpool(data: IDataType): Promise<string>
xxhash32(data: IDataType, seed?: number): Promise<string>
xxhash64(data: IDataType, seedLow?: number, seedHigh?: number): Promise<string>
xxhash3(data: IDataType, seedLow?: number, seedHigh?: number): Promise<string>
xxhash128(data: IDataType, seedLow?: number, seedHigh?: number): Promise<string>
interface IHasher {
init: () => IHasher;
update: (data: IDataType) => IHasher;
digest: (outputType: 'hex' | 'binary') => string | Uint8Array;
save: () => Uint8Array;
load: (state: Uint8Array) => IHasher;
blockSize: number;
digestSize: number;
}
createAdler32(): Promise<IHasher>
createBLAKE2b(bits?: number, key?: IDataType): Promise<IHasher>
createBLAKE2s(bits?: number, key?: IDataType): Promise<IHasher>
createBLAKE3(bits?: number, key?: IDataType): Promise<IHasher>
createCRC32(polynomial?: number): Promise<IHasher>
createCRC64(polynomial?: number): Promise<IHasher>
createKeccak(bits?: 224 | 256 | 384 | 512): Promise<IHasher>
createMD4(): Promise<IHasher>
createMD5(): Promise<IHasher>
createRIPEMD160(): Promise<IHasher>
createSHA1(): Promise<IHasher>
createSHA224(): Promise<IHasher>
createSHA256(): Promise<IHasher>
createSHA3(bits?: 224 | 256 | 384 | 512): Promise<IHasher>
createSHA384(): Promise<IHasher>
createSHA512(): Promise<IHasher>
createSM3(): Promise<IHasher>
createWhirlpool(): Promise<IHasher>
createXXHash32(seed: number): Promise<IHasher>
createXXHash64(seedLow: number, seedHigh: number): Promise<IHasher>
createXXHash3(seedLow: number, seedHigh: number): Promise<IHasher>
createXXHash128(seedLow: number, seedHigh: number): Promise<IHasher>
createHMAC(hashFunction: Promise<IHasher>, key: IDataType): Promise<IHasher>
pbkdf2({
password: IDataType,
salt: IDataType,
iterations: number,
hashLength: number,
hashFunction: Promise<IHasher>,
outputType?: 'hex' | 'binary',
}): Promise<string | Uint8Array>
scrypt({
password: IDataType,
salt: IDataType,
costFactor: number,
blockSize: number,
parallelism: number,
hashLength: number,
outputType?: 'hex' | 'binary',
}): Promise<string | Uint8Array>
interface IArgon2Options {
password: IDataType;
salt: IDataType;
secret?: IDataType;
iterations: number;
parallelism: number;
memorySize: number;
hashLength: number;
outputType?: 'hex' | 'binary' | 'encoded';
}
argon2i(options: IArgon2Options): Promise<string | Uint8Array>
argon2d(options: IArgon2Options): Promise<string | Uint8Array>
argon2id(options: IArgon2Options): Promise<string | Uint8Array>
argon2Verify({
password: IDataType,
secret?: IDataType,
hash: string,
}): Promise<boolean>
bcrypt({
password: IDataType,
salt: IDataType,
costFactor: number,
outputType?: 'hex' | 'binary' | 'encoded',
}): Promise<string | Uint8Array>
bcryptVerify({
password: IDataType,
hash: string,
}): Promise<boolean>
Future plans
- Add more well-known algorithms
- Write a polyfill which keeps bundle sizes low and enables running binaries containing newer WASM instructions
- Use WebAssembly Bulk Memory Operations
- Use WebAssembly SIMD instructions (expecting a 10-20% performance increase)
- Enable multithreading where it's possible (like at Argon2)
4.12.0 (November 19, 2024)
- BREAKING CHANGE: crc32c() and createCRC32C() were removed. You can now do the same thing with crc32() and createCRC32() through setting the polynomial parameter to 0x82F63B78
- Added CRC-64 hash function
- Exported IDataType, IHasher types from index.ts
- Update benchmarks
- Update dependencies, including Clang
- Migrate from ESLint to Biome and fix linting errors