js-sha3
js-sha3 is a dependency-free JavaScript implementation of the SHA-3 family and related NIST SP 800-185 functions. It exposes SHA3-224 through SHA3-512, legacy Keccak variants, variable-length SHAKE and cSHAKE, keyed KMAC, TupleHash, and ParallelHash. Calls can be one-shot or incremental, accept strings and common byte containers, and return hex, number arrays, or ArrayBuffer values in browsers, Node.js, and web workers.
Install js-sha3 when a browser-compatible protocol specifically needs SHA-3, Keccak, or an SP 800-185 function. For ordinary Node hashing or password storage, built-in crypto or a real password KDF is the better fit.
Use it if
- You need the same SHA-3 or Keccak implementation in browsers, Node.js, and web workers without native bindings
- You need SP 800-185 functions such as cSHAKE, KMAC, TupleHash, or ParallelHash that common runtime crypto APIs may not expose
- You must process data incrementally and choose hex, number-array, or ArrayBuffer output
- You maintain a protocol that explicitly requires Keccak rather than standardized SHA-3 and need both names available
- You only hash data in Node.js: node:crypto already exposes SHA-3 on supported OpenSSL builds, avoids another dependency, and can use native code rather than this JavaScript implementation
- You are storing passwords: the exported type definitions provide hash, XOF, KMAC, TupleHash, and ParallelHash operations but no salt, memory cost, or password KDF; use Argon2, scrypt, or bcrypt instead
- You can treat SHA3-256 and Keccak-256 as interchangeable: the README explains that pre-0.2 sha3 names became keccak names, and their padding differs, so Ethereum-style Keccak vectors do not match standardized SHA-3
- You want an API that measures XOF output in bytes: SHAKE, cSHAKE, KMAC, TupleHash, and ParallelHash all take outputBits, so passing 32 requests four output bytes rather than a 32-byte digest
- You require a mature major-version compatibility promise or a published security audit: the current release is still 0.13.0, and the repository documents CI and test vectors but does not claim an independent cryptographic audit
Setup reality
npm install js-sha3 is the whole install: there are no runtime dependencies, native builds, credentials, peer packages, or config files. Version 0.13.0 has an exports map for ESM and CommonJS plus bundled TypeScript declarations, so import { sha3_256 } from 'js-sha3' and require('js-sha3') are both supported. Browser users without a bundler must load a build file and use the globals it exposes. The hard part is choosing the exact function and data representation, not installation. JavaScript strings are encoded as UTF-8, while number[], Uint8Array, and ArrayBuffer are treated as bytes. SHA3 and Keccak are different standardized constructions despite similar names. Every variable-length family takes an output length in bits. cSHAKE adds function-name and customization inputs; KMAC puts the key first; ParallelHash adds a block-size argument; TupleHash treats each item as a separate framed input rather than concatenating them. Incremental objects are finalized by hex, array, digest, arrayBuffer, or toString, and the README warns that update after finalization throws. The old buffer output method is deprecated in favor of arrayBuffer. For streamed TupleHash input, beginInput needs the exact byte length before updateChunk calls, which is not the same as a JavaScript string's character count for non-ASCII text.
Patterns
Hash UTF-8 text with SHA3-256hash-text-sha3
import { sha3_256 } from 'js-sha3';
const hex = sha3_256('The quick brown fox');
console.log(hex);String input is encoded as UTF-8 and the direct call returns lowercase hexadecimal. SHA3-256 is not the same function as Keccak-256.
Compute a legacy Keccak-256 digesthash-keccak-256
import { keccak256 } from 'js-sha3';
const digest = keccak256('message');Use this only when the protocol says Keccak-256, as Ethereum tooling often does. Standard SHA3-256 uses different domain padding and yields another digest.
Hash exact bytes instead of texthash-byte-array
import { sha3_256 } from 'js-sha3';
const bytes = new Uint8Array([0x00, 0xff, 0x10, 0x80]);
const hex = sha3_256(bytes);Uint8Array, ArrayBuffer, and number[] inputs are treated as byte data, avoiding accidental UTF-8 encoding of a textual representation.
Return binary digest outputreturn-array-buffer
import { sha3_512 } from 'js-sha3';
const buffer = sha3_512.arrayBuffer('message');
const bytes = new Uint8Array(buffer);Use arrayBuffer rather than the old buffer method, which the README marks deprecated because its name is easily confused with Node.js Buffer.
Build a digest incrementallystream-hash-input
import { sha3_256 } from 'js-sha3';
const hash = sha3_256.create();
hash.update(headerBytes);
hash.update(bodyChunk1);
hash.update(bodyChunk2);
const hex = hash.hex();Calling hex finalizes the object. Version 0.8.0 and later throw if update is called after any final output operation.
Generate variable-length SHAKE outputgenerate-shake-output
import { shake256 } from 'js-sha3';
const output256 = shake256('domain-separated input', 256);
const output512 = shake256('domain-separated input', 512);The second argument is bits, not bytes: 256 produces 32 bytes of output and 512 produces 64 bytes.
Domain-separate output with cSHAKEcustomize-cshake
import { cshake128 } from 'js-sha3';
const hex = cshake128(
'payload',
256,
'ExampleFunction',
'my-app/v1'
);Function name and customization are part of the construction, not labels appended afterward. Both sides must use identical byte values.
Compute KMAC128authenticate-with-kmac
import { kmac128 } from 'js-sha3';
const tag = kmac128(
secretKeyBytes,
messageBytes,
256,
'payments/v1'
);The argument order is key, message, output bits, customization. Keep the key as bytes from a secure source, not a memorable password string.
Hash framed tuple itemshash-structured-tuple
import { tuplehash128 } from 'js-sha3';
const digest = tuplehash128(
['abc', 'd'],
256,
'records/v1'
);TupleHash preserves item boundaries, so ['abc', 'd'] is deliberately distinct from ['ab', 'cd'] even though concatenated text matches.
Stream known-length TupleHash itemsstream-tuple-input
import { tuplehash128 } from 'js-sha3';
const hash = tuplehash128.create(256, 'records/v1');
hash.beginInput(3).updateChunk([0x61, 0x62]).updateChunk([0x63]);
hash.beginInput(1).updateChunk([0x64]);
const digest = hash.hex();beginInput takes the exact byte length of the next tuple item. For non-ASCII strings, character count is not a safe substitute for UTF-8 byte length.
Hash a large value with ParallelHashparallel-hash-blocks
import { parallelhash128 } from 'js-sha3';
const blockSize = 1024;
const outputBits = 256;
const digest = parallelhash128(data, blockSize, outputBits, 'archive/v1');Version 0.13.0 introduced ParallelHash. Its second argument is the fixed block size, while the third is the output length in bits.
Compare MAC bytes without early exit in Nodecompare-node-digests
import { timingSafeEqual } from 'node:crypto';
import { kmac256 } from 'js-sha3';
const actual = Buffer.from(kmac256.array(key, message, 256, 'api/v1'));
const expected = Buffer.from(expectedHex, 'hex');
const valid = actual.length === expected.length && timingSafeEqual(actual, expected);A normal === comparison exits early. timingSafeEqual requires equal-length buffers, so check lengths before calling it.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @noble/hashes | npm | You want audited-style, modern ESM primitives with SHA-3 plus a wider set of hash and KDF functions |
| hash-wasm | npm | You hash large browser payloads and prefer WebAssembly implementations with streaming interfaces |
| crypto-js | npm | You are maintaining an older CryptoJS-based codebase and need its WordArray conventions across several algorithms |