hash-wasm
Hash-WASM是一个⚡超快速⚡的哈希函数库,适用于浏览器和Node.js。 它使用经过手动优化的WebAssembly二进制文件来计算哈希值,比其他库更快。
支持的算法
名称 | 打包大小(gzip压缩后) |
---|---|
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, CRC32C | 3 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 |
特性
- 比其他JS / WASM实现快得多(见下方基准测试)
- 轻量级。参见上表
- 从高度优化的C语言算法编译而来
- 支持所有现代浏览器、Node.js和Deno
- 支持大型数据流
- 支持UTF-8字符串和类型化数组
- 支持分块输入流
- 模块化架构(算法被编译成独立的WASM二进制文件)
- WASM模块以base64字符串形式打包(没有链接问题)
- 支持树摇(Webpack只打包你使用的哈希算法)
- 无需Webpack或其他打包工具即可工作
- 包含TypeScript类型定义
- 可在Web Workers中工作
- 零依赖
- 支持使用多个状态进行并发哈希计算
- 支持保存和加载哈希的内部状态(分段哈希和回溯)
- 所有算法都有单元测试
- 100%开源且透明的构建过程
- 易于使用的基于Promise的API
安装
npm i hash-wasm
也可以直接在HTML中使用(通过jsDelivr):
<!-- 将所有算法加载到全局变量 `hashwasm` 中 -->
<script src="https://cdn.jsdelivr.net/npm/hash-wasm@4"></script>
<!-- 将单个算法加载到全局变量 `hashwasm` 中 -->
<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>
示例
演示应用
使用简写形式
这是计算哈希的最简单和最快速的方法。当输入缓冲区已经在内存中时使用它。
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();
* 参见字符串编码陷阱
** 参见API参考
使用流式输入的高级用法
createXXXX() 函数创建具有单独状态的新WASM实例,可用于并行计算多个哈希值。与重用相同WASM实例和状态进行多次计算的简写函数(如md5())相比,它们的速度较慢。因此,当数据已经在内存中时,始终首选简写形式。
为了获得最佳性能,避免在循环中调用createXXXX()函数。当顺序计算多个哈希值时,可以使用init()函数在运行之间重置内部状态。这比使用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'); // 返回Uint8Array
console.log('SHA1:', hash);
}
run();
* 参见字符串编码陷阱
** 参见API参考
使用Argon2对密码进行哈希处理
选择参数的推荐过程可以在这里找到: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, // salt是包含随机字节的缓冲区
parallelism: 1,
iterations: 256,
memorySize: 512, // 使用512KB内存
hashLength: 32, // 输出大小 = 32字节
outputType: 'encoded', // 返回包含验证密钥所需参数的标准编码字符串
});
console.log('派生密钥:', key);
const isValid = await argon2Verify({
password: 'pass',
hash: key,
});
console.log(isValid ? '密码有效' : '密码无效');
}
run();
* 参见字符串编码陷阱
** 参见API参考
使用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, // salt是包含16个随机字节的缓冲区
costFactor: 11,
outputType: 'encoded', // 返回包含验证密钥所需参数的标准编码字符串
});
console.log('派生密钥:', key);
const isValid = await bcryptVerify({
password: 'pass',
hash: key,
});
console.log(isValid ? '密码有效' : '密码无效');
}
run();
* 参见字符串编码陷阱
** 参见API参考
计算HMAC
所有支持的哈希函数都可用于计算HMAC。为了获得最佳性能,避免在循环中调用createXXXX()(参见上面的"使用流式输入的高级用法"部分)
import { createHMAC, createSHA3 } from 'hash-wasm';
async function run() {
const hashFunc = createSHA3(224); // SHA3-224
const hmac = await createHMAC(hashFunc, 'key');
const fruits = ['apple', 'raspberry', 'watermelon'];
console.log('输入:', fruits);
const codes = fruits.map(data => {
hmac.init();
hmac.update(data);
return hmac.digest();
});
console.log('HMAC:', codes);
}
run();
* 参见字符串编码陷阱
** 参见API参考
计算PBKDF2
所有支持的哈希函数都可用于计算PBKDF2。为了获得最佳性能,避免在循环中调用createXXXX()(参见上面的"使用流式输入的高级用法"部分)
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('派生密钥:', key);
}
run();
* 参见字符串编码陷阱
** 参见API参考
字符串编码陷阱
你应该注意到,一个给定的字符串可能有多种UTF-8表示:
'\u00fc' // 编码ü字符
'u\u0308' // 也编码ü字符
'\u00fc' === 'u\u0308' // false
'ü' === 'ü' // false
此库中定义的所有算法都依赖于输入字符串的二进制表示。因此,强烈建议在将字符串传递给hash-wasm之前对其进行规范化。你可以使用内置的String函数normalize()
来实现这一点:
'\u00fc'.normalize() === 'u\u0308'.normalize() // true
const te = new TextEncoder();
te.encode('u\u0308'); // Uint8Array(3) [117, 204, 136]
te.encode('\u00fc'); // Uint8Array(2) [195, 188]
te.encode('u\u0308'.normalize('NFKC')); // Uint8Array(2) [195, 188]
te.encode('\u00fc'.normalize('NFKC')); // Uint8Array(2) [195, 188]
你可以在这里阅读更多关于这个问题的信息:https://en.wikipedia.org/wiki/Unicode_equivalence
可恢复的哈希计算
你可以使用.save()
函数保存哈希的当前内部状态。这个状态可以写入磁盘或存储在内存的其他位置。
然后你可以使用.load(state)
函数将该状态重新加载到哈希的新实例中,或者加载回同一个实例。
这允许你将哈希文件的工作跨多个进程进行(例如在像AWS Lambda这样执行时间有限的环境中,大型作业需要跨多个调用分割),或者将哈希回退到流中的早期点。例如,第一个进程可以:
// 第一个进程开始哈希计算
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()); // 输出 6cd3556deb0da54bca060b4c39479839 = md5("Hello, world!")
注意,保存和加载进程必须运行兼容版本的哈希函数(即在保存和加载进程中使用的hash-wasm版本之间哈希函数没有变化)。如果保存的状态不兼容,load()
将抛出异常。
保存的状态可能包含有关输入的信息,包括明文输入字节,因此从安全角度来看,它必须与输入数据本身一样谨慎处理。
浏览器支持
Chrome | Safari | Firefox | Edge | IE | Node.js | Deno |
---|---|---|---|---|---|---|
57+ | 11+ | 53+ | 16+ | 不支持 | 8+ | 1+ |
基准测试
你可以在这里进行自己的测量:链接
测量了两种场景:
- 短格式的吞吐量(输入大小 = 32字节)
- 长格式的吞吐量(输入大小 = 1MB)
结果:
MD5 | 吞吐量(32字节) | 吞吐量(1MB) |
---|---|---|
hash-wasm 4.4.1 | 57.43 MB/s | 596.64 MB/s |
spark-md5 3.0.1 (来自npm) | 28.08 MB/s (慢2.0倍) | 110.12 MB/s (慢5.4倍) |
md5-wasm 2.0.0 (来自npm) | 16.49 MB/s (慢3.4倍) | 74.43 MB/s (慢8.0倍) |
crypto-js 4.0.0 (来自npm) | 3.80 MB/s (慢15倍) | 26.70 MB/s (慢22倍) |
node-forge 0.10.0 (来自npm) | 9.28 MB/s (慢6.2倍) | 12.27 MB/s (慢49倍) |
md5 2.3.0 (来自npm) | 7.66 MB/s (慢7.5倍) | 11.42 MB/s (慢52倍) |
SHA1 | 吞吐量(32字节) | 吞吐量(1MB) |
---|---|---|
hash-wasm 4.4.1 | 47.97 MB/s | 649.13 MB/s |
jsSHA 3.2.0 (来自npm) | 6.15 MB/s (慢7.8倍) | 46.13 MB/s (慢14倍) |
crypto-js 4.0.0 (来自npm) | 4.10 MB/s (慢12倍) | 40.36 MB/s (慢16倍) |
sha1 1.1.1 (来自npm) | 6.86 MB/s (慢7.0倍) | 12.46 MB/s (慢52倍) |
node-forge 0.10.0 (来自npm) | 8.71 MB/s (慢5.5倍) | 12.86 MB/s (慢50倍) |
SHA256 | 吞吐量(32字节) | 吞吐量(1MB) |
---|---|---|
hash-wasm 4.4.1 | 35.67 MB/s | 254.40 MB/s |
sha256-wasm 2.1.2 (来自npm) | 17.83 MB/s (慢2倍) | 164.13 MB/s (慢1.5倍) |
jsSHA 3.2.0 (来自npm) | 5.57 MB/s (慢6.4倍) | 35.81 MB/s (慢7.1倍) |
crypto-js 4.0.0 (来自npm) | 3.51 MB/s (慢10倍) | 36.48 MB/s (慢7倍) |
node-forge 0.10.0 (来自npm) | 6.81 MB/s (慢5.2倍) | 11.91 MB/s (慢21倍) |
SHA3-512 | 吞吐量(32字节) | 吞吐量(1MB) |
---|---|---|
hash-wasm 4.4.1 | 22.91 MB/s | 177.16 MB/s |
sha3-wasm 1.0.0 (来自npm) | 7.16 MB/s (慢3.2倍) | 74.75 MB/s (慢2.4倍) |
sha3 2.1.4 (来自npm) | 2.00 MB/s (慢11倍) | 6.48 MB/s (慢27倍) |
jsSHA 3.2.0 (来自npm) | 0.93 MB/s (慢24倍) | 2.09 MB/s (慢85倍) |
XXHash64 | 吞吐量(32字节) | 吞吐量(1MB) |
---|---|---|
hash-wasm 4.4.1 | 88.33 MB/s | 12 012.74 MB/s |
xxhash-wasm 0.4.1 (来自npm) | 28.44 MB/s (慢3.1倍) | 11 296.84 MB/s |
xxhashjs 0.2.2 (来自npm) | 0.37 MB/s (慢239倍) | 17.95 MB/s (慢669倍) |
PBKDF2-SHA512 - 1000次迭代 | 每秒操作次数(16字节) |
---|---|
hash-wasm 4.4.1 | 348 ops |
pbkdf2 3.1.1 (来自npm) | 55 ops (慢6.3倍) |
crypto-js 4.0.0 (来自npm) | 13 ops (慢27倍) |
Argon2id (m=512, t=8, p=1) | 每秒操作次数(16字节) |
---|---|
hash-wasm 4.4.1 | 256 ops |
argon2-browser 1.15.3 (来自npm) | 104 ops (慢2.5倍) |
argon2-wasm 0.9.0 (来自npm) | 101 ops (慢2.5倍) |
argon2-wasm-pro 1.1.0 (来自npm) | 100 ops (慢2.5倍) |
* 这些测量是在Kaby Lake桌面CPU上使用Chrome v89
进行的。
API
type IDataType = string | Buffer | Uint8Array | Uint16Array | Uint32Array;
// 所有函数返回十六进制格式的哈希值
adler32(data: IDataType): Promise<string>
blake2b(data: IDataType, bits?: number, key?: IDataType): Promise<string> // 默认为512位
blake2s(data: IDataType, bits?: number, key?: IDataType): Promise<string> // 默认为256位
blake3(data: IDataType, bits?: number, key?: IDataType): Promise<string> // 默认为256位
crc32(data: IDataType): Promise<string>
crc32c(data: IDataType): Promise<string>
keccak(data: IDataType, bits?: 224 | 256 | 384 | 512): Promise<string> // 默认为512位
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> // 默认为512位
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> // 默认为512位
createBLAKE2s(bits?: number, key?: IDataType): Promise<IHasher> // 默认为256位
createBLAKE3(bits?: number, key?: IDataType): Promise<IHasher> // 默认为256位
createCRC32(): Promise<IHasher>
createCRC32C(): Promise<IHasher>
createKeccak(bits?: 224 | 256 | 384 | 512): Promise<IHasher> // 默认为512位
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> // 默认为512位
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>, // 像createSHA1()这样函数的返回值
outputType?: 'hex' | 'binary', // 默认返回十六进制字符串
}): Promise<string | Uint8Array>
scrypt({
password: IDataType, // 要哈希的密码(或消息)
salt: IDataType, // 盐(通常包含随机字节)
costFactor: number, // CPU/内存成本 - 必须是2的幂(如1024)
blockSize: number, // 块大小参数(通常使用8)
parallelism: number, // 并行度
hashLength: number, // 输出大小(字节)
outputType?: 'hex' | 'binary', // 默认返回十六进制字符串
}): Promise<string | Uint8Array>
interface IArgon2Options {
password: IDataType; // 要哈希的密码(或消息)
salt: IDataType; // 盐(通常包含随机字节)
secret?: IDataType; // 用于键控哈希的密钥
iterations: number; // 执行迭代的次数
parallelism: number; // 并行度
memorySize: number; // 要使用的内存量(以kibibytes为单位,1024字节)
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, // 盐(16字节长 - 通常包含随机字节)
costFactor: number, // 执行迭代的次数(4 - 31)
outputType?: 'hex' | 'binary' | 'encoded', // 默认返回编码字符串
}): Promise<string | Uint8Array>
bcryptVerify({
password: IDataType, // 密码
hash: string, // 编码后的哈希
}): Promise<boolean>
未来计划
=====
- 添加更多知名算法
- 编写一个多填充程序,保持打包大小小,并支持运行包含较新WASM指令的二进制文件
- 使用WebAssembly批量内存操作
- 使用WebAssembly SIMD指令(预期性能提升10-20%)
- 在可能的情况下启用多线程(如Argon2)