hash-wasm

repository·master·Indexed 22 days ago

https://github.com/daninet/hash-wasm

A high-performance hashing library for browsers and Node.js using hand-tuned WebAssembly binaries. It supports a wide range of algorithms including MD4, MD5, SHA-1, SHA-2, SHA-3, Keccak, BLAKE2, BLAKE3, PBKDF2, Argon2, bcrypt, scrypt, Adler-32, CRC32, CRC32C, RIPEMD-160, HMAC, xxHash, SM3, and Whirlpool. The library provides shorthand functions for single-shot hashing and an IHasher interface for streaming, incremental, and resumable hashing via .save() and .load() methods.

Tokens
11.4K
Snippets
41
Records
46
Agent score
78%

What's inside hash-wasm

  1. Normalize strings to avoid encoding pitfalls

    master

    All algorithms in hash-wasm depend on the binary representation of the input string. Because different Unicode sequences can represent the same character (e.g., \u00fc vs u\u0308), you should normalize your strings before hashing to ensure consistent results.

    It is highly recommended to use the built-in String.prototype.normalize() method, specifically with the "NFKC" form, before encoding with TextEncoder.

    // Example of normalization
    const te = new TextEncoder();
    const str1 = "u\u0308";
    const str2 = "\u00fc";
    
    // Without normalization, these might produce different hashes
    te.encode(str1.normalize("NFKC"));
    te.encode(str2.normalize("NFKC"));
  2. Perform resumable hashing with .save() and .load()

    master

    You can pause and resume hashing by saving and loading the internal state of a hash instance. This is useful for splitting large jobs across multiple processes (like AWS Lambda) or rewinding a stream.

    Workflow:

    1. Use an instance created via createXXXX().
    2. Call .save() to capture the current internal state. This state can be stored in memory or on disk.
    3. To resume, create a new instance and call .load(state) with the previously saved state.
    4. Continue with .update() and .digest().

    Security Warning: The saved state may contain plaintext input bytes. Treat the saved state with the same level of security as the input data itself.

    Compatibility Note: Both the saving and loading processes must use compatible versions of the hash function. If the version of hash-wasm changes in a way that alters the internal state format, .load() will throw an exception.

    // first process starts hashing
    const md5 = await createMD5();
    md5.init();
    md5.update("Hello, ");
    const state = md5.save(); // save this state
    
    // second process resumes hashing from the stored state
    const md5 = await createMD5();
    md5.load(state);
    md5.update("world!");
    console.log(md5.digest()); // Prints 6cd3556deb0da54bca060b4c39479839 = md5("Hello, world!")
  3. Use hash-wasm via CDN in HTML

    master

    You can use hash-wasm directly in the browser without a bundler by loading it via jsDelivr.

    To load all algorithms into the global hashwasm variable, use the main bundle. To load specific algorithms to keep the footprint small, load the individual UMD files.

    <!-- load all algortihms into the global `hashwasm` variable -->
    <script src="https://cdn.jsdelivr.net/npm/hash-wasm@4"></script>
    
    <!-- load individual algortihms into the global `hashwasm` variable -->
    <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>
  4. Use streaming input with createXXXX() functions

    master

    For large inputs or streaming data, use createXXXX() functions (e.g., createSHA1()). These create new WASM instances with separate states, allowing for parallel hashing.

    Performance Tip: Avoid calling createXXXX() in loops. Instead, use the .init() method to reset the internal state of an existing instance between sequential runs. This is significantly faster than creating new instances.

    Workflow:

    1. Call await createXXXX() to get an instance.
    2. Call .init() to prepare the state.
    3. Call .update(chunk) repeatedly for each data chunk.
    4. Call .digest(outputType) to get the final hash. outputType can be "binary" (returns Uint8Array) or other formats defined in the API.
    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"); // returns Uint8Array
      console.log("SHA1:", hash);
    }
    
    run();
  5. Use the shorthand form for fast hashing

    master

    The shorthand form is the easiest and fastest way to calculate hashes when the input buffer is already in memory. It reuses the same WASM instance and state to perform multiple calculations, making it more efficient than creating new instances for every call.

    Supported shorthand functions include md5, sha1, sha512, and sha3 (which accepts a bit length as a second argument). These functions accept strings or typed arrays (e.g., Uint8Array, Uint32Array) as input.

    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();
  6. Calculate HMAC

    master

    HMAC can be calculated using any supported hash function. To optimize performance, create the hash function instance once and reuse it by calling .init() between different inputs.

    Workflow:

    1. Create a hash function instance using createXXXX().
    2. Create an HMAC instance using await createHMAC(hashFunc, "key").
    3. For each input, call hmac.init(), hmac.update(data), and hmac.digest().
    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("Input:", fruits);
    
      const codes = fruits.map((data) => {
        hmac.init();
        hmac.update(data);
        return hmac.digest();
      });
    
      console.log("HMAC:", codes);
    }
    
    run();
  7. Calculate PBKDF2

    master

    Use the pbkdf2 function to derive keys. It requires a hash function instance created via createXXXX().

    Parameters for pbkdf2:

    • password: The input string.
    • salt: A Uint8Array.
    • iterations: Number of iterations.
    • hashLength: Desired output size in bytes.
    • hashFunction: An instance of a hash function (e.g., from createSHA1()).
    • outputType: The desired output format (e.g., "hex").
    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();
  8. Calculate PBKDF2 or Scrypt hashes

    master

    For key derivation, use pbkdf2 or scrypt.

    pbkdf2 options:

    • password: Password/message.
    • salt: Salt.
    • iterations: Number of iterations.
    • hashLength: Output size in bytes.
    • hashFunction: A Promise<IHasher> (e.g., createSHA1()).
    • outputType: 'hex' | 'binary' (default 'hex').

    scrypt options:

    • password: Password/message.
    • salt: Salt.
    • costFactor: CPU/memory cost (must be a power of 2, e.g., 1024).
    • blockSize: Block size parameter (8 is common).
    • parallelism: Degree of parallelism.
    • hashLength: Output size in bytes.
    • outputType: 'hex' | 'binary' (default 'hex').
  9. Hash passwords with Argon2

    master

    Argon2 is a memory-hard password hashing function. You can use argon2i, argon2d, or argon2id variants.

    Options (IArgon2Options):

    • password: The password/message to hash.
    • salt: Random salt.
    • secret: (Optional) Secret for keyed hashing.
    • iterations: Number of iterations.
    • parallelism: Degree of parallelism.
    • memorySize: Memory to use in kibibytes (e.g., 1024).
    • hashLength: Output size in bytes.
    • outputType: 'hex' | 'binary' | 'encoded' (default 'hex').

    To verify a password against an encoded hash, use argon2Verify.

    const hash = await argon2id({
      password: 'password',
      salt: 'salt',
      iterations: 2,
      memorySize: 1024,
      parallelism: 1,
      hashLength: 32
    });
    
    const isValid = await argon2Verify({
      password: 'password',
      hash: hash // the encoded string
    });
  10. Use the IHasher interface for streaming or incremental hashing

    master

    When dealing with large datasets that cannot be loaded into memory at once, use the create[Algorithm] factory functions to obtain an IHasher instance. This allows you to update the hash incrementally.

    IHasher Interface Methods:

    • init(): Resets the hasher.
    • update(data: IDataType): Feeds more data into the hasher. Returns the hasher instance for chaining.
    • digest(outputType: 'hex' | 'binary'): Finalizes the hash. Returns a hex string (default) or Uint8Array.
    • save(): Returns the internal state as a Uint8Array for later resumption.
    • load(state: Uint8Array): Loads a previously saved internal state.

    Properties:

    • blockSize: The block size in bytes.
    • digestSize: The digest size in bytes.

    Factory Examples:

    • createSHA256(): Promise<IHasher>
    • createMD5(): Promise<IHasher>
    • createXXHash32(seed: number): Promise<IHasher>
    • createHMAC(hashFunction: Promise<IHasher>, key: IDataType): Promise<IHasher>
    const hasher = await createSHA256();
    hasher.update('part 1');
    hasher.update('part 2');
    const result = hasher.digest('hex');
  11. Hash passwords with bcrypt

    master

    Bcrypt is a password hashing function based on the Blowfish cipher. Use bcrypt to create a hash and bcryptVerify to check it.

    bcrypt options:

    • password: The password to hash.
    • salt: 16-byte salt.
    • costFactor: Number of iterations (4 - 31).
    • outputType: 'hex' | 'binary' | 'encoded' (default 'encoded').

    bcryptVerify options:

    • password: The password to check.
    • hash: The encoded hash string.
    const hash = await bcrypt({
      password: 'my-password',
      salt: 'random-salt-16b',
      costFactor: 10
    });
    
    const isValid = await bcryptVerify({
      password: 'my-password',
      hash: hash
    });