CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-noble--hashes

Audited & minimal 0-dependency JS implementation of SHA, RIPEMD, BLAKE, HMAC, HKDF, PBKDF & Scrypt

Overview
Eval results
Files

webcrypto.mddocs/

WebCrypto: Native Browser Cryptography Wrapper

A friendly wrapper over the native WebCrypto API (crypto.subtle) that provides a noble-hashes compatible interface. Uses browser/Node.js native implementations for better performance while maintaining API consistency with the pure JavaScript implementations.

Imports

import { sha256, sha384, sha512, hmac, hkdf, pbkdf2 } from '@noble/hashes/webcrypto.js';

Important Notes

  • All WebCrypto operations are asynchronous (return Promises)
  • Uses native browser/Node.js implementations
  • Faster than pure JS but less control
  • Limited to SHA-2 hash family (no SHA-3, BLAKE, etc.)
  • Not all functions from noble-hashes are available

Capabilities

Hash Functions

WebCrypto wrapper provides SHA-256, SHA-384, and SHA-512 with noble-hashes compatible interface.

/**
 * WebCrypto SHA-256 hash function
 * @param msg - Input data as Uint8Array
 * @returns Promise resolving to 32-byte hash digest
 */
async function sha256(msg: Uint8Array): Promise<Uint8Array>;

// Properties
sha256.webCryptoName: 'SHA-256';  // Native algorithm name
sha256.outputLen: 32;              // Output length in bytes
sha256.blockLen: 64;               // Block length in bytes

/**
 * WebCrypto SHA-384 hash function
 * @param msg - Input data as Uint8Array
 * @returns Promise resolving to 48-byte hash digest
 */
async function sha384(msg: Uint8Array): Promise<Uint8Array>;

// Properties
sha384.webCryptoName: 'SHA-384';
sha384.outputLen: 48;
sha384.blockLen: 128;

/**
 * WebCrypto SHA-512 hash function
 * @param msg - Input data as Uint8Array
 * @returns Promise resolving to 64-byte hash digest
 */
async function sha512(msg: Uint8Array): Promise<Uint8Array>;

// Properties
sha512.webCryptoName: 'SHA-512';
sha512.outputLen: 64;
sha512.blockLen: 128;

/**
 * WebHash type for hash functions
 */
interface WebHash {
  (msg: Uint8Array): Promise<Uint8Array>;
  webCryptoName: string;
  outputLen: number;
  blockLen: number;
}

HMAC

HMAC implementation using native WebCrypto for better performance.

/**
 * HMAC using WebCrypto
 * @param hash - WebCrypto hash function (sha256, sha384, or sha512)
 * @param key - Secret key as Uint8Array
 * @param message - Message to authenticate as Uint8Array
 * @returns Promise resolving to HMAC tag
 */
async function hmac(
  hash: WebHash,
  key: Uint8Array,
  message: Uint8Array
): Promise<Uint8Array>;

HKDF

HKDF key derivation using native WebCrypto.

/**
 * HKDF key derivation using WebCrypto
 * @param hash - WebCrypto hash function (sha256, sha384, or sha512)
 * @param ikm - Input keying material as Uint8Array
 * @param salt - Optional salt (defaults to hash.outputLen zeros)
 * @param info - Optional context information
 * @param length - Desired output length in bytes
 * @returns Promise resolving to derived key material
 */
async function hkdf(
  hash: WebHash,
  ikm: Uint8Array,
  salt: Uint8Array | undefined,
  info: Uint8Array | undefined,
  length: number
): Promise<Uint8Array>;

PBKDF2

PBKDF2 password-based key derivation using native WebCrypto.

/**
 * PBKDF2 using WebCrypto
 * @param hash - WebCrypto hash function (sha256, sha384, or sha512)
 * @param password - Password as string or Uint8Array
 * @param salt - Salt as string or Uint8Array
 * @param opts - Options
 * @param opts.c - Iteration count (>= 1)
 * @param opts.dkLen - Desired key length in bytes (default: 32)
 * @returns Promise resolving to derived key
 */
async function pbkdf2(
  hash: WebHash,
  password: string | Uint8Array,
  salt: string | Uint8Array,
  opts: { c: number; dkLen?: number }
): Promise<Uint8Array>;

Usage Examples

Basic Hashing

import { sha256, sha384, sha512 } from '@noble/hashes/webcrypto.js';
import { bytesToHex } from '@noble/hashes/utils.js';

// WebCrypto hashing (async)
const data = Uint8Array.from([0xca, 0xfe, 0x01, 0x23]);

const hash256 = await sha256(data);
console.log(bytesToHex(hash256)); // 32-byte hash

const hash384 = await sha384(data);
console.log(bytesToHex(hash384)); // 48-byte hash

const hash512 = await sha512(data);
console.log(bytesToHex(hash512)); // 64-byte hash

HMAC Authentication

import { sha256, hmac } from '@noble/hashes/webcrypto.js';
import { randomBytes, bytesToHex } from '@noble/hashes/utils.js';

const key = randomBytes(32);
const message = new Uint8Array([1, 2, 3, 4, 5]);

// Compute HMAC-SHA256 using native crypto
const tag = await hmac(sha256, key, message);
console.log(bytesToHex(tag)); // 32-byte tag

// Verify HMAC
async function verifyHMAC(
  key: Uint8Array,
  message: Uint8Array,
  expectedTag: Uint8Array
): Promise<boolean> {
  const computedTag = await hmac(sha256, key, message);

  if (computedTag.length !== expectedTag.length) return false;

  let diff = 0;
  for (let i = 0; i < computedTag.length; i++) {
    diff |= computedTag[i] ^ expectedTag[i];
  }
  return diff === 0;
}

HKDF Key Derivation

import { sha256, hkdf } from '@noble/hashes/webcrypto.js';
import { randomBytes, utf8ToBytes } from '@noble/hashes/utils.js';

// Derive keys from shared secret
const sharedSecret = randomBytes(32);
const salt = randomBytes(32);
const info = utf8ToBytes('application-key-v1');

// Derive 32-byte key
const derivedKey = await hkdf(sha256, sharedSecret, salt, info, 32);

// Derive multiple keys with different contexts
async function deriveMultipleKeys(ikm: Uint8Array, salt: Uint8Array) {
  const encKey = await hkdf(
    sha256,
    ikm,
    salt,
    utf8ToBytes('encryption'),
    32
  );

  const authKey = await hkdf(
    sha256,
    ikm,
    salt,
    utf8ToBytes('authentication'),
    32
  );

  return { encKey, authKey };
}

PBKDF2 Password Hashing

import { sha256, pbkdf2 } from '@noble/hashes/webcrypto.js';
import { randomBytes } from '@noble/hashes/utils.js';

// Hash password with PBKDF2
const password = 'user-password';
const salt = randomBytes(16);

const hash = await pbkdf2(sha256, password, salt, {
  c: 100000,  // 100k iterations
  dkLen: 32   // 32-byte output
});

// Verify password
async function verifyPassword(
  password: string,
  salt: Uint8Array,
  expectedHash: Uint8Array
): Promise<boolean> {
  const computedHash = await pbkdf2(sha256, password, salt, {
    c: 100000,
    dkLen: expectedHash.length
  });

  if (computedHash.length !== expectedHash.length) return false;

  let diff = 0;
  for (let i = 0; i < computedHash.length; i++) {
    diff |= computedHash[i] ^ expectedHash[i];
  }
  return diff === 0;
}

Performance Comparison

// Pure JS version
import { sha256 as sha256Pure } from '@noble/hashes/sha2.js';

// WebCrypto version
import { sha256 as sha256Web } from '@noble/hashes/webcrypto.js';

const data = new Uint8Array(1024 * 1024); // 1MB

// Pure JS (synchronous)
console.time('Pure JS');
const hashPure = sha256Pure(data);
console.timeEnd('Pure JS');
// Pure JS: ~3ms

// WebCrypto (asynchronous, but often faster)
console.time('WebCrypto');
const hashWeb = await sha256Web(data);
console.timeEnd('WebCrypto');
// WebCrypto: ~1ms (native implementation)

Fallback Pattern

import { sha256 as sha256Pure } from '@noble/hashes/sha2.js';
import { sha256 as sha256Web } from '@noble/hashes/webcrypto.js';

// Use WebCrypto if available, fallback to pure JS
async function hashWithFallback(data: Uint8Array): Promise<Uint8Array> {
  try {
    // Try WebCrypto first (faster)
    return await sha256Web(data);
  } catch (error) {
    // Fallback to pure JS if WebCrypto unavailable
    return sha256Pure(data);
  }
}

Batch Hashing

import { sha256 } from '@noble/hashes/webcrypto.js';

// Hash multiple items in parallel
async function hashBatch(items: Uint8Array[]): Promise<Uint8Array[]> {
  return Promise.all(items.map(item => sha256(item)));
}

// Usage
const items = [
  new Uint8Array([1, 2, 3]),
  new Uint8Array([4, 5, 6]),
  new Uint8Array([7, 8, 9])
];

const hashes = await hashBatch(items);
console.log('Hashed', hashes.length, 'items');

Technical Details

WebCrypto API

WebCrypto is the browser standard for cryptographic operations:

  • Available in modern browsers via crypto.subtle
  • Available in Node.js 15+ via globalThis.crypto
  • Provides native implementations (C/C++/assembly)
  • Always asynchronous

Performance

WebCrypto is typically 1.5-5x faster than pure JavaScript:

  • Uses native compiled code
  • Can leverage CPU instructions (AES-NI, SHA extensions)
  • Optimized by browser/runtime vendors
  • Results vary by browser and operation

Approximate speedups (browser dependent):

  • SHA-256: 1.5-2x faster
  • SHA-512: 2-3x faster
  • HMAC: 2x faster
  • PBKDF2: 1.5x faster (native is still slow)

Limitations

Hash Functions:

  • Only SHA-256, SHA-384, SHA-512 available
  • No SHA-3, Keccak, BLAKE, or legacy hashes
  • No incremental hashing (no .create() method)

No Scrypt or Argon2:

  • WebCrypto doesn't provide these
  • Must use pure JS implementations

Always Async:

  • Cannot use in synchronous code
  • Adds complexity with Promise handling
  • May not be suitable for all use cases

No Streaming:

  • Must have entire input in memory
  • Cannot process data incrementally
  • Pure JS version has .create().update() for streaming

Browser Compatibility:

  • Requires modern browsers
  • May not work in older environments
  • Always check availability

Advantages

Performance: Native implementation is faster for large inputs

Security: Browser-maintained code, less attack surface

Standards Compliance: Official W3C standard

Resource Efficient: Offloads work to native code

Disadvantages

Async Only: Requires async/await or Promise handling

Limited Algorithms: Only SHA-2 family available

No Incremental Hashing: Cannot stream data

Browser Dependent: Performance and availability vary

Less Control: Cannot inspect internal state

When to Use WebCrypto

Use WebCrypto when:

  • Performance is critical
  • Processing large amounts of data
  • Only need SHA-2 family
  • Running in browser or modern Node.js
  • Can handle async operations

Use Pure JS when:

  • Need SHA-3, BLAKE, or other algorithms
  • Need incremental/streaming hashing
  • Must be synchronous
  • Need consistent cross-platform behavior
  • Need to inspect internal state
  • Maximum control required

Availability Check

// Check if WebCrypto is available
function isWebCryptoAvailable(): boolean {
  return typeof globalThis.crypto !== 'undefined' &&
         typeof globalThis.crypto.subtle !== 'undefined';
}

if (isWebCryptoAvailable()) {
  console.log('WebCrypto available');
  // Use webcrypto.js
} else {
  console.log('WebCrypto not available');
  // Use pure JS implementations
}

Browser Compatibility

WebCrypto is available in:

  • Chrome 37+
  • Firefox 34+
  • Safari 11+
  • Edge 79+
  • Node.js 15+
  • Deno (all versions)

Not available in:

  • Internet Explorer
  • Older Node.js versions (< 15)
  • Some restricted environments

Security Considerations

Timing Attacks: Native implementations may have better constant-time guarantees than JavaScript, but this is implementation-dependent.

Side Channels: Native code may be more vulnerable to some side-channel attacks (Spectre, Meltdown) but less vulnerable to timing attacks.

Auditing: Pure JS code is auditable; native code is maintained by browser/runtime vendors.

Supply Chain: Relying on browser crypto reduces your attack surface but trusts browser vendors.

Comparison Table

FeatureWebCryptoPure JS
PerformanceFaster (1.5-5x)Slower
AlgorithmsSHA-2 onlyAll algorithms
IncrementalNoYes
Async/SyncAsync onlyBoth
AvailabilityModern onlyUniversal
AuditabilityBrowser codeAudited source
ControlLimitedFull

Migration Example

// Before (Pure JS)
import { sha256 } from '@noble/hashes/sha2.js';
import { hmac } from '@noble/hashes/hmac.js';

const hash = sha256(data);
const tag = hmac(sha256, key, message);

// After (WebCrypto, async)
import { sha256, hmac } from '@noble/hashes/webcrypto.js';

const hash = await sha256(data);
const tag = await hmac(sha256, key, message);

References

  • Web Cryptography API Specification: W3C Standard
  • MDN Web Crypto API: Documentation and examples
  • Node.js Crypto: Node.js WebCrypto documentation

Install with Tessl CLI

npx tessl i tessl/npm-noble--hashes@2.0.0

docs

argon2.md

blake.md

eskdf.md

hkdf.md

hmac.md

index.md

legacy.md

pbkdf2.md

scrypt.md

sha2.md

sha3-addons.md

sha3.md

utils.md

webcrypto.md

tile.json