SHA-224 in JavaScript: Complete Guide

Master SHA-224 implementation in JavaScript for Node.js and browsers. Learn WebCrypto API, streaming, Web Workers, TypeScript integration, and production-ready patterns.

Table of Contents

  1. Node.js Implementation
  2. Browser Implementation
  3. WebCrypto API
  4. Streaming & Large Files
  5. Web Workers for Performance
  6. TypeScript Integration
  7. Framework Integration
  8. Testing Strategies
  9. Performance Optimization
  10. Production Patterns

1. Node.js Implementation

Basic Node.js Crypto Module Usage

JavaScript - Node.js
const crypto = require('crypto');
const fs = require('fs');
const { pipeline } = require('stream');
const { promisify } = require('util');

/**
 * SHA-224 Hasher class for Node.js
 */
class SHA224Hasher {
  /**
   * Hash a string
   * @param {string} data - String to hash
   * @param {string} encoding - Input encoding (default: 'utf8')
   * @returns {string} Hex digest
   */
  static hashString(data, encoding = 'utf8') {
    return crypto
      .createHash('sha224')
      .update(data, encoding)
      .digest('hex');
  }

  /**
   * Hash a buffer
   * @param {Buffer} buffer - Buffer to hash
   * @returns {string} Hex digest
   */
  static hashBuffer(buffer) {
    return crypto
      .createHash('sha224')
      .update(buffer)
      .digest('hex');
  }

  /**
   * Hash a file synchronously
   * @param {string} filepath - Path to file
   * @returns {string} Hex digest
   */
  static hashFileSync(filepath) {
    const fileBuffer = fs.readFileSync(filepath);
    return this.hashBuffer(fileBuffer);
  }

  /**
   * Hash a file asynchronously
   * @param {string} filepath - Path to file
   * @returns {Promise} Promise resolving to hex digest
   */
  static async hashFile(filepath) {
    return new Promise((resolve, reject) => {
      const hash = crypto.createHash('sha224');
      const stream = fs.createReadStream(filepath);

      stream.on('data', chunk => hash.update(chunk));
      stream.on('end', () => resolve(hash.digest('hex')));
      stream.on('error', reject);
    });
  }

  /**
   * Hash a file using streams (most efficient)
   * @param {string} filepath - Path to file
   * @returns {Promise} Promise resolving to hex digest
   */
  static async hashFileStream(filepath) {
    const hash = crypto.createHash('sha224');
    const stream = fs.createReadStream(filepath);
    const pipelineAsync = promisify(pipeline);

    await pipelineAsync(stream, hash);
    return hash.digest('hex');
  }

  /**
   * Hash multiple chunks incrementally
   * @param {Array} chunks - Array of data chunks
   * @returns {string} Hex digest
   */
  static hashChunks(chunks) {
    const hash = crypto.createHash('sha224');

    for (const chunk of chunks) {
      hash.update(chunk);
    }

    return hash.digest('hex');
  }

  /**
   * Create HMAC-SHA224
   * @param {string} key - Secret key
   * @param {string} data - Data to authenticate
   * @returns {string} HMAC hex digest
   */
  static createHMAC(key, data) {
    return crypto
      .createHmac('sha224', key)
      .update(data)
      .digest('hex');
  }
}

// Usage examples
async function examples() {
  // Hash string
  const stringHash = SHA224Hasher.hashString('Hello World');
  console.log('String hash:', stringHash);

  // Hash buffer
  const bufferHash = SHA224Hasher.hashBuffer(Buffer.from('Binary data'));
  console.log('Buffer hash:', bufferHash);

  // Hash file (async)
  try {
    const fileHash = await SHA224Hasher.hashFile('./example.txt');
    console.log('File hash:', fileHash);
  } catch (error) {
    console.error('Error hashing file:', error);
  }

  // HMAC
  const hmac = SHA224Hasher.createHMAC('secret-key', 'message');
  console.log('HMAC:', hmac);

  // Incremental hashing
  const chunks = ['Part 1', 'Part 2', 'Part 3'];
  const chunkedHash = SHA224Hasher.hashChunks(chunks);
  console.log('Chunked hash:', chunkedHash);
}

// examples();

module.exports = SHA224Hasher;

Advanced Stream Processing

JavaScript - Node.js Streams
const crypto = require('crypto');
const fs = require('fs');
const { Transform, Readable } = require('stream');
const { pipeline } = require('stream/promises');

/**
 * Custom transform stream for hashing with progress
 */
class HashTransform extends Transform {
  constructor(algorithm = 'sha224', onProgress) {
    super();
    this.hash = crypto.createHash(algorithm);
    this.bytesProcessed = 0;
    this.onProgress = onProgress;
    this.startTime = Date.now();
  }

  _transform(chunk, encoding, callback) {
    this.hash.update(chunk);
    this.bytesProcessed += chunk.length;

    if (this.onProgress) {
      const elapsed = (Date.now() - this.startTime) / 1000;
      const throughput = this.bytesProcessed / elapsed / 1048576; // MB/s

      this.onProgress({
        bytesProcessed: this.bytesProcessed,
        elapsedTime: elapsed,
        throughputMBps: throughput
      });
    }

    // Pass through the data unchanged
    callback(null, chunk);
  }

  _flush(callback) {
    this.push(this.hash.digest('hex'));
    callback();
  }

  getHash() {
    return this.hash.digest('hex');
  }
}

/**
 * Advanced streaming SHA-224 hasher
 */
class StreamingHasher {
  /**
   * Hash file with progress reporting
   * @param {string} filepath - Path to file
   * @param {Function} onProgress - Progress callback
   * @returns {Promise} Hash result and statistics
   */
  static async hashFileWithProgress(filepath, onProgress) {
    const fileSize = fs.statSync(filepath).size;
    let lastProgress = 0;

    const hashStream = new HashTransform('sha224', (stats) => {
      const progress = (stats.bytesProcessed / fileSize) * 100;

      // Report progress at intervals
      if (progress - lastProgress >= 1 || progress === 100) {
        lastProgress = progress;
        onProgress({
          ...stats,
          progress: Math.min(progress, 100),
          fileSize
        });
      }
    });

    const readStream = fs.createReadStream(filepath, { highWaterMark: 1048576 }); // 1MB chunks
    const writeStream = fs.createWriteStream('/dev/null'); // Discard output

    const startTime = Date.now();

    await pipeline(readStream, hashStream, writeStream);

    const duration = (Date.now() - startTime) / 1000;

    return {
      hash: hashStream.getHash(),
      fileSize,
      duration,
      throughput: fileSize / duration / 1048576 // MB/s
    };
  }

  /**
   * Hash multiple files in parallel
   * @param {Array} filepaths - Array of file paths
   * @param {number} concurrency - Max parallel operations
   * @returns {Promise} Map of filepath to hash
   */
  static async hashMultipleFiles(filepaths, concurrency = 4) {
    const results = new Map();
    const queue = [...filepaths];
    const workers = [];

    const worker = async () => {
      while (queue.length > 0) {
        const filepath = queue.shift();
        try {
          const hash = crypto.createHash('sha224');
          const stream = fs.createReadStream(filepath);

          await pipeline(stream, hash);
          results.set(filepath, hash.digest('hex'));
        } catch (error) {
          results.set(filepath, { error: error.message });
        }
      }
    };

    // Start workers
    for (let i = 0; i < Math.min(concurrency, filepaths.length); i++) {
      workers.push(worker());
    }

    await Promise.all(workers);
    return results;
  }

  /**
   * Hash data from async generator
   * @param {AsyncGenerator} generator - Async generator yielding chunks
   * @returns {Promise} Hex digest
   */
  static async hashAsyncGenerator(generator) {
    const hash = crypto.createHash('sha224');

    for await (const chunk of generator) {
      hash.update(chunk);
    }

    return hash.digest('hex');
  }

  /**
   * Create hash stream that can be piped
   * @returns {HashTransform} Transform stream
   */
  static createHashStream() {
    return new HashTransform('sha224');
  }
}

// Example: Hash large file with progress bar
async function hashWithProgressBar() {
  const filepath = './large-file.bin';

  const result = await StreamingHasher.hashFileWithProgress(filepath, (stats) => {
    const progressBar = '='.repeat(Math.floor(stats.progress / 2)) +
                       '-'.repeat(50 - Math.floor(stats.progress / 2));

    process.stdout.write(`\r[${progressBar}] ${stats.progress.toFixed(1)}% @ ${stats.throughputMBps.toFixed(2)} MB/s`);
  });

  console.log('\n\nResult:', result);
}

// Example: Async generator
async function* dataGenerator() {
  for (let i = 0; i < 100; i++) {
    await new Promise(resolve => setTimeout(resolve, 10));
    yield Buffer.from(`Chunk ${i}\n`);
  }
}

async function hashGeneratorExample() {
  const hash = await StreamingHasher.hashAsyncGenerator(dataGenerator());
  console.log('Generator hash:', hash);
}

module.exports = StreamingHasher;
        
      
    

    
    

2. Browser Implementation

Pure JavaScript Implementation (No Dependencies)

JavaScript - Browser
/**
 * Pure JavaScript SHA-224 implementation for browsers
 * No external dependencies required
 */
class SHA224 {
  constructor() {
    // SHA-224 initial hash values
    this.H = [
      0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
      0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4
    ];

    // SHA-224 round constants
    this.K = [
      0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
      0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
      0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
      0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
      0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
      0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
      0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
      0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
      0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
      0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
      0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
      0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
      0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
      0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
      0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
      0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
    ];
  }

  /**
   * Right rotate 32-bit integer
   */
  rightRotate(value, amount) {
    return ((value >>> amount) | (value << (32 - amount))) >>> 0;
  }

  /**
   * Convert string to byte array
   */
  stringToBytes(str) {
    const encoder = new TextEncoder();
    return encoder.encode(str);
  }

  /**
   * Convert byte array to hex string
   */
  bytesToHex(bytes) {
    return Array.from(bytes)
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
  }

  /**
   * Pad message according to SHA-2 specification
   */
  padMessage(bytes) {
    const bitLength = bytes.length * 8;
    const padded = [...bytes];

    // Append single '1' bit (plus seven '0' bits)
    padded.push(0x80);

    // Pad with zeros until length ≡ 448 (mod 512)
    while ((padded.length % 64) !== 56) {
      padded.push(0);
    }

    // Append length as 64-bit big-endian integer
    for (let i = 7; i >= 0; i--) {
      padded.push((bitLength >>> (i * 8)) & 0xff);
    }

    return padded;
  }

  /**
   * Process a single 512-bit block
   */
  processBlock(block) {
    const W = new Array(64);

    // Prepare message schedule
    for (let t = 0; t < 16; t++) {
      W[t] = (block[t * 4] << 24) |
             (block[t * 4 + 1] << 16) |
             (block[t * 4 + 2] << 8) |
             (block[t * 4 + 3]);
    }

    for (let t = 16; t < 64; t++) {
      const s0 = this.rightRotate(W[t - 15], 7) ^
                 this.rightRotate(W[t - 15], 18) ^
                 (W[t - 15] >>> 3);
      const s1 = this.rightRotate(W[t - 2], 17) ^
                 this.rightRotate(W[t - 2], 19) ^
                 (W[t - 2] >>> 10);
      W[t] = (W[t - 16] + s0 + W[t - 7] + s1) >>> 0;
    }

    // Initialize working variables
    let [a, b, c, d, e, f, g, h] = this.H;

    // Main loop
    for (let t = 0; t < 64; t++) {
      const S1 = this.rightRotate(e, 6) ^
                 this.rightRotate(e, 11) ^
                 this.rightRotate(e, 25);
      const ch = (e & f) ^ (~e & g);
      const temp1 = (h + S1 + ch + this.K[t] + W[t]) >>> 0;

      const S0 = this.rightRotate(a, 2) ^
                 this.rightRotate(a, 13) ^
                 this.rightRotate(a, 22);
      const maj = (a & b) ^ (a & c) ^ (b & c);
      const temp2 = (S0 + maj) >>> 0;

      h = g;
      g = f;
      f = e;
      e = (d + temp1) >>> 0;
      d = c;
      c = b;
      b = a;
      a = (temp1 + temp2) >>> 0;
    }

    // Update hash values
    this.H[0] = (this.H[0] + a) >>> 0;
    this.H[1] = (this.H[1] + b) >>> 0;
    this.H[2] = (this.H[2] + c) >>> 0;
    this.H[3] = (this.H[3] + d) >>> 0;
    this.H[4] = (this.H[4] + e) >>> 0;
    this.H[5] = (this.H[5] + f) >>> 0;
    this.H[6] = (this.H[6] + g) >>> 0;
    this.H[7] = (this.H[7] + h) >>> 0;
  }

  /**
   * Hash input data
   * @param {string|Uint8Array|ArrayBuffer} input - Data to hash
   * @returns {string} Hex digest
   */
  hash(input) {
    // Reset hash values
    this.H = [
      0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
      0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4
    ];

    // Convert input to bytes
    let bytes;
    if (typeof input === 'string') {
      bytes = this.stringToBytes(input);
    } else if (input instanceof Uint8Array) {
      bytes = input;
    } else if (input instanceof ArrayBuffer) {
      bytes = new Uint8Array(input);
    } else {
      throw new TypeError('Input must be string, Uint8Array, or ArrayBuffer');
    }

    // Pad message
    const padded = this.padMessage(bytes);

    // Process each 512-bit block
    for (let i = 0; i < padded.length; i += 64) {
      this.processBlock(padded.slice(i, i + 64));
    }

    // Produce final hash (truncate to 224 bits)
    let result = '';
    for (let i = 0; i < 7; i++) {
      result += this.H[i].toString(16).padStart(8, '0');
    }

    return result;
  }

  /**
   * Hash file from File object
   * @param {File} file - File to hash
   * @param {Function} onProgress - Progress callback
   * @returns {Promise} Hex digest
   */
  async hashFile(file, onProgress) {
    const chunkSize = 1048576; // 1MB chunks
    let offset = 0;

    // Reset hash values
    this.H = [
      0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
      0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4
    ];

    const reader = new FileReader();
    const chunks = [];

    while (offset < file.size) {
      const chunk = file.slice(offset, offset + chunkSize);

      await new Promise((resolve, reject) => {
        reader.onload = (e) => {
          const bytes = new Uint8Array(e.target.result);
          chunks.push(...bytes);

          if (onProgress) {
            const progress = Math.min((offset + chunkSize) / file.size * 100, 100);
            onProgress(progress);
          }

          resolve();
        };

        reader.onerror = reject;
        reader.readAsArrayBuffer(chunk);
      });

      offset += chunkSize;
    }

    // Process accumulated chunks
    const padded = this.padMessage(chunks);

    for (let i = 0; i < padded.length; i += 64) {
      this.processBlock(padded.slice(i, i + 64));
    }

    // Return final hash
    let result = '';
    for (let i = 0; i < 7; i++) {
      result += this.H[i].toString(16).padStart(8, '0');
    }

    return result;
  }
}

// Usage examples
const sha224 = new SHA224();

// Hash string
const stringHash = sha224.hash('Hello World');
console.log('String hash:', stringHash);

// Hash binary data
const binaryData = new Uint8Array([1, 2, 3, 4, 5]);
const binaryHash = sha224.hash(binaryData);
console.log('Binary hash:', binaryHash);

// Hash file (with file input element)
document.getElementById('fileInput')?.addEventListener('change', async (e) => {
  const file = e.target.files[0];
  if (file) {
    const hash = await sha224.hashFile(file, (progress) => {
      console.log(`Progress: ${progress.toFixed(1)}%`);
    });
    console.log('File hash:', hash);
  }
});

3. WebCrypto API

Modern Browser WebCrypto Implementation

JavaScript - WebCrypto API
/**
 * WebCrypto API SHA-224 implementation
 * Works in modern browsers with native crypto support
 */
class WebCryptoSHA224 {
  /**
   * Check if WebCrypto is available
   */
  static isSupported() {
    return typeof crypto !== 'undefined' &&
           crypto.subtle &&
           typeof crypto.subtle.digest === 'function';
  }

  /**
   * Hash string using WebCrypto
   * @param {string} text - Text to hash
   * @returns {Promise} Hex digest
   */
  static async hashString(text) {
    if (!this.isSupported()) {
      throw new Error('WebCrypto API not supported');
    }

    const encoder = new TextEncoder();
    const data = encoder.encode(text);
    const hashBuffer = await crypto.subtle.digest('SHA-224', data);

    return this.bufferToHex(hashBuffer);
  }

  /**
   * Hash ArrayBuffer
   * @param {ArrayBuffer} buffer - Buffer to hash
   * @returns {Promise} Hex digest
   */
  static async hashBuffer(buffer) {
    if (!this.isSupported()) {
      throw new Error('WebCrypto API not supported');
    }

    const hashBuffer = await crypto.subtle.digest('SHA-224', buffer);
    return this.bufferToHex(hashBuffer);
  }

  /**
   * Hash Blob/File
   * @param {Blob|File} blob - Blob or File to hash
   * @param {Function} onProgress - Progress callback
   * @returns {Promise} Hex digest
   */
  static async hashBlob(blob, onProgress) {
    if (!this.isSupported()) {
      throw new Error('WebCrypto API not supported');
    }

    const chunkSize = 1048576; // 1MB chunks
    let offset = 0;
    const chunks = [];

    while (offset < blob.size) {
      const chunk = blob.slice(offset, Math.min(offset + chunkSize, blob.size));
      const arrayBuffer = await chunk.arrayBuffer();
      chunks.push(new Uint8Array(arrayBuffer));

      offset += chunkSize;

      if (onProgress) {
        const progress = (offset / blob.size) * 100;
        onProgress(Math.min(progress, 100));
      }
    }

    // Combine all chunks
    const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
    const combined = new Uint8Array(totalLength);
    let position = 0;

    for (const chunk of chunks) {
      combined.set(chunk, position);
      position += chunk.length;
    }

    const hashBuffer = await crypto.subtle.digest('SHA-224', combined);
    return this.bufferToHex(hashBuffer);
  }

  /**
   * Stream hash using ReadableStream
   * @param {ReadableStream} stream - Stream to hash
   * @returns {Promise} Hex digest
   */
  static async hashStream(stream) {
    if (!this.isSupported()) {
      throw new Error('WebCrypto API not supported');
    }

    const reader = stream.getReader();
    const chunks = [];

    try {
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        chunks.push(value);
      }
    } finally {
      reader.releaseLock();
    }

    // Combine chunks
    const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
    const combined = new Uint8Array(totalLength);
    let position = 0;

    for (const chunk of chunks) {
      combined.set(chunk, position);
      position += chunk.length;
    }

    const hashBuffer = await crypto.subtle.digest('SHA-224', combined);
    return this.bufferToHex(hashBuffer);
  }

  /**
   * Convert ArrayBuffer to hex string
   * @param {ArrayBuffer} buffer - Buffer to convert
   * @returns {string} Hex string
   */
  static bufferToHex(buffer) {
    const byteArray = new Uint8Array(buffer);
    return Array.from(byteArray)
      .map(byte => byte.toString(16).padStart(2, '0'))
      .join('');
  }

  /**
   * Create HMAC-SHA224
   * @param {string} key - Secret key
   * @param {string} message - Message to authenticate
   * @returns {Promise} HMAC hex digest
   */
  static async createHMAC(key, message) {
    if (!this.isSupported()) {
      throw new Error('WebCrypto API not supported');
    }

    const encoder = new TextEncoder();
    const keyData = encoder.encode(key);
    const messageData = encoder.encode(message);

    // Import key
    const cryptoKey = await crypto.subtle.importKey(
      'raw',
      keyData,
      { name: 'HMAC', hash: 'SHA-224' },
      false,
      ['sign']
    );

    // Sign message
    const signature = await crypto.subtle.sign(
      'HMAC',
      cryptoKey,
      messageData
    );

    return this.bufferToHex(signature);
  }

  /**
   * Verify data integrity
   * @param {string} data - Data to verify
   * @param {string} expectedHash - Expected hash
   * @returns {Promise} True if hash matches
   */
  static async verify(data, expectedHash) {
    const actualHash = await this.hashString(data);
    return this.constantTimeCompare(actualHash, expectedHash);
  }

  /**
   * Constant-time string comparison
   * @param {string} a - First string
   * @param {string} b - Second string
   * @returns {boolean} True if strings are equal
   */
  static constantTimeCompare(a, b) {
    if (a.length !== b.length) return false;

    let result = 0;
    for (let i = 0; i < a.length; i++) {
      result |= a.charCodeAt(i) ^ b.charCodeAt(i);
    }

    return result === 0;
  }
}

// Usage examples
async function webCryptoExamples() {
  if (!WebCryptoSHA224.isSupported()) {
    console.error('WebCrypto not supported in this browser');
    return;
  }

  try {
    // Hash string
    const stringHash = await WebCryptoSHA224.hashString('Hello World');
    console.log('String hash:', stringHash);

    // Hash ArrayBuffer
    const buffer = new Uint8Array([1, 2, 3, 4, 5]).buffer;
    const bufferHash = await WebCryptoSHA224.hashBuffer(buffer);
    console.log('Buffer hash:', bufferHash);

    // HMAC
    const hmac = await WebCryptoSHA224.createHMAC('secret', 'message');
    console.log('HMAC:', hmac);

    // Verify
    const isValid = await WebCryptoSHA224.verify(
      'Hello World',
      '4149da18aa8bfc2b1e382c6c26556d01a92c261b6436dad5e3be3fcc'
    );
    console.log('Verification:', isValid);

  } catch (error) {
    console.error('WebCrypto error:', error);
  }
}

webCryptoExamples();

5. Web Workers for Performance

Offload Hashing to Web Workers

JavaScript - Main Thread
/**
 * SHA-224 Web Worker Manager
 * Offloads heavy hashing operations to background threads
 */
class SHA224WorkerManager {
  constructor(workerCount = 4) {
    this.workers = [];
    this.taskQueue = [];
    this.busyWorkers = new Set();

    // Initialize worker pool
    for (let i = 0; i < workerCount; i++) {
      this.createWorker();
    }
  }

  /**
   * Create a new worker
   */
  createWorker() {
    // Create worker from blob URL (inline code)
    const workerCode = `
      // SHA-224 implementation for Web Worker
      class SHA224Worker {
        constructor() {
          this.H = [
            0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
            0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4
          ];

          this.K = [
            0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
            0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
            0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
            0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
            0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
            0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
            0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
            0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
            0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
            0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
            0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
            0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
            0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
            0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
            0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
            0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
          ];
        }

        rightRotate(value, amount) {
          return ((value >>> amount) | (value << (32 - amount))) >>> 0;
        }

        hash(data) {
          // Reset hash values
          this.H = [
            0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
            0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4
          ];

          // Process data (simplified for brevity)
          const bytes = new TextEncoder().encode(data);
          const padded = this.padMessage(bytes);

          for (let i = 0; i < padded.length; i += 64) {
            this.processBlock(padded.slice(i, i + 64));
          }

          // Return hash
          let result = '';
          for (let i = 0; i < 7; i++) {
            result += this.H[i].toString(16).padStart(8, '0');
          }
          return result;
        }

        padMessage(bytes) {
          const bitLength = bytes.length * 8;
          const padded = [...bytes];
          padded.push(0x80);

          while ((padded.length % 64) !== 56) {
            padded.push(0);
          }

          for (let i = 7; i >= 0; i--) {
            padded.push((bitLength >>> (i * 8)) & 0xff);
          }

          return padded;
        }

        processBlock(block) {
          // SHA-224 block processing (same as before)
          // ... implementation details ...
        }
      }

      const hasher = new SHA224Worker();

      // Handle messages from main thread
      self.addEventListener('message', async (e) => {
        const { id, type, data } = e.data;

        try {
          let result;

          switch (type) {
            case 'hash':
              result = hasher.hash(data);
              break;

            case 'hashBatch':
              result = data.map(item => hasher.hash(item));
              break;

            default:
              throw new Error('Unknown operation: ' + type);
          }

          self.postMessage({ id, success: true, result });

        } catch (error) {
          self.postMessage({
            id,
            success: false,
            error: error.message
          });
        }
      });
    `;

    const blob = new Blob([workerCode], { type: 'application/javascript' });
    const workerUrl = URL.createObjectURL(blob);
    const worker = new Worker(workerUrl);

    worker.addEventListener('message', (e) => {
      this.handleWorkerMessage(worker, e.data);
    });

    this.workers.push(worker);
    return worker;
  }

  /**
   * Handle message from worker
   */
  handleWorkerMessage(worker, message) {
    const { id, success, result, error } = message;
    const task = this.taskQueue.find(t => t.id === id);

    if (task) {
      if (success) {
        task.resolve(result);
      } else {
        task.reject(new Error(error));
      }

      // Remove task and mark worker as available
      this.taskQueue = this.taskQueue.filter(t => t.id !== id);
      this.busyWorkers.delete(worker);

      // Process next queued task
      this.processQueue();
    }
  }

  /**
   * Hash single item
   * @param {string} data - Data to hash
   * @returns {Promise} Hex digest
   */
  hash(data) {
    return new Promise((resolve, reject) => {
      const task = {
        id: Math.random().toString(36).substr(2, 9),
        type: 'hash',
        data,
        resolve,
        reject
      };

      this.taskQueue.push(task);
      this.processQueue();
    });
  }

  /**
   * Hash multiple items in parallel
   * @param {Array} items - Items to hash
   * @returns {Promise>} Array of hex digests
   */
  async hashBatch(items) {
    const batchSize = Math.ceil(items.length / this.workers.length);
    const batches = [];

    for (let i = 0; i < items.length; i += batchSize) {
      batches.push(items.slice(i, i + batchSize));
    }

    const promises = batches.map(batch => {
      return new Promise((resolve, reject) => {
        const task = {
          id: Math.random().toString(36).substr(2, 9),
          type: 'hashBatch',
          data: batch,
          resolve,
          reject
        };

        this.taskQueue.push(task);
        this.processQueue();
      });
    });

    const results = await Promise.all(promises);
    return results.flat();
  }

  /**
   * Process queued tasks
   */
  processQueue() {
    const availableWorkers = this.workers.filter(w => !this.busyWorkers.has(w));

    while (availableWorkers.length > 0 && this.taskQueue.length > 0) {
      const worker = availableWorkers.shift();
      const task = this.taskQueue.find(t => !t.processing);

      if (task) {
        task.processing = true;
        this.busyWorkers.add(worker);

        worker.postMessage({
          id: task.id,
          type: task.type,
          data: task.data
        });
      }
    }
  }

  /**
   * Terminate all workers
   */
  terminate() {
    this.workers.forEach(worker => worker.terminate());
    this.workers = [];
    this.taskQueue = [];
    this.busyWorkers.clear();
  }
}

// Usage example
async function workerExample() {
  const manager = new SHA224WorkerManager(4); // 4 workers

  try {
    // Single hash
    const hash = await manager.hash('Hello World');
    console.log('Single hash:', hash);

    // Batch hashing
    const items = Array(100).fill(0).map((_, i) => `Item ${i}`);
    console.time('Batch hash');
    const hashes = await manager.hashBatch(items);
    console.timeEnd('Batch hash');
    console.log(`Hashed ${hashes.length} items`);

  } finally {
    manager.terminate();
  }
}

// workerExample();

6. TypeScript Integration

Type-Safe SHA-224 Implementation

TypeScript
/**
 * TypeScript SHA-224 implementation with full type safety
 */

// Type definitions
interface HashResult {
  hash: string;
  algorithm: 'SHA-224';
  timestamp: Date;
  inputSize: number;
}

interface HashOptions {
  encoding?: BufferEncoding;
  outputFormat?: 'hex' | 'base64' | 'buffer';
}

interface ProgressCallback {
  (progress: {
    bytesProcessed: number;
    totalBytes: number;
    percentComplete: number;
    estimatedTimeRemaining: number;
  }): void;
}

type HashInput = string | Buffer | Uint8Array | ArrayBuffer;

// Custom errors
class HashError extends Error {
  constructor(message: string, public code: string) {
    super(message);
    this.name = 'HashError';
  }
}

/**
 * Type-safe SHA-224 hasher class
 */
class TypedSHA224Hasher {
  private readonly algorithm = 'sha224' as const;

  /**
   * Hash input with type checking
   */
  public hash(input: HashInput, options?: HashOptions): string | Buffer {
    const buffer = this.normalizeInput(input);
    const crypto = require('crypto');
    const hash = crypto.createHash(this.algorithm);

    hash.update(buffer);

    const outputFormat = options?.outputFormat || 'hex';

    switch (outputFormat) {
      case 'hex':
        return hash.digest('hex');
      case 'base64':
        return hash.digest('base64');
      case 'buffer':
        return hash.digest();
      default:
        const _exhaustive: never = outputFormat;
        throw new HashError(`Invalid output format: ${outputFormat}`, 'INVALID_FORMAT');
    }
  }

  /**
   * Hash with detailed result
   */
  public hashWithMetadata(input: HashInput): HashResult {
    const startTime = Date.now();
    const buffer = this.normalizeInput(input);
    const hash = this.hash(buffer) as string;

    return {
      hash,
      algorithm: 'SHA-224',
      timestamp: new Date(),
      inputSize: buffer.length
    };
  }

  /**
   * Async hash with progress tracking
   */
  public async hashAsync(
    input: HashInput,
    onProgress?: ProgressCallback
  ): Promise {
    return new Promise((resolve, reject) => {
      try {
        const buffer = this.normalizeInput(input);
        const crypto = require('crypto');
        const hash = crypto.createHash(this.algorithm);

        const chunkSize = 65536;
        let processed = 0;
        const total = buffer.length;
        const startTime = Date.now();

        const processChunk = () => {
          const chunk = buffer.slice(processed, processed + chunkSize);
          hash.update(chunk);
          processed += chunk.length;

          if (onProgress) {
            const elapsed = Date.now() - startTime;
            const rate = processed / elapsed;
            const remaining = (total - processed) / rate;

            onProgress({
              bytesProcessed: processed,
              totalBytes: total,
              percentComplete: (processed / total) * 100,
              estimatedTimeRemaining: remaining
            });
          }

          if (processed < total) {
            setImmediate(processChunk);
          } else {
            resolve(hash.digest('hex'));
          }
        };

        processChunk();

      } catch (error) {
        reject(new HashError(
          `Failed to hash input: ${error.message}`,
          'HASH_FAILED'
        ));
      }
    });
  }

  /**
   * Verify hash with constant-time comparison
   */
  public verify(input: HashInput, expectedHash: string): boolean {
    const actualHash = this.hash(input) as string;

    if (actualHash.length !== expectedHash.length) {
      return false;
    }

    return this.constantTimeCompare(actualHash, expectedHash);
  }

  /**
   * Create HMAC-SHA224
   */
  public createHMAC(key: string | Buffer, data: HashInput): string {
    const crypto = require('crypto');
    const normalizedData = this.normalizeInput(data);

    return crypto
      .createHmac(this.algorithm, key)
      .update(normalizedData)
      .digest('hex');
  }

  /**
   * Hash multiple inputs
   */
  public async hashMultiple(
    inputs: HashInput[],
    parallel: boolean = true
  ): Promise {
    if (parallel) {
      return Promise.all(inputs.map(input => this.hashAsync(input)));
    } else {
      const results: string[] = [];
      for (const input of inputs) {
        results.push(await this.hashAsync(input));
      }
      return results;
    }
  }

  /**
   * Normalize input to Buffer
   */
  private normalizeInput(input: HashInput): Buffer {
    if (typeof input === 'string') {
      return Buffer.from(input, 'utf8');
    } else if (Buffer.isBuffer(input)) {
      return input;
    } else if (input instanceof Uint8Array) {
      return Buffer.from(input);
    } else if (input instanceof ArrayBuffer) {
      return Buffer.from(input);
    } else {
      throw new HashError(
        `Invalid input type: ${typeof input}`,
        'INVALID_INPUT'
      );
    }
  }

  /**
   * Constant-time string comparison
   */
  private constantTimeCompare(a: string, b: string): boolean {
    let result = 0;
    for (let i = 0; i < a.length; i++) {
      result |= a.charCodeAt(i) ^ b.charCodeAt(i);
    }
    return result === 0;
  }
}

// Generic hash function with type constraints
function createHasher(
  algorithm: T
): TypedSHA224Hasher {
  if (algorithm !== 'sha224') {
    throw new Error(`This implementation only supports SHA-224`);
  }
  return new TypedSHA224Hasher();
}

// Usage with type safety
async function typeScriptExample(): Promise {
  const hasher = new TypedSHA224Hasher();

  // Type-safe hashing
  const stringHash = hasher.hash('Hello World');
  console.log('String hash:', stringHash);

  // With metadata
  const result = hasher.hashWithMetadata(Buffer.from('Binary data'));
  console.log('Result:', result);

  // Async with progress
  const largeData = Buffer.alloc(10485760); // 10MB
  const hash = await hasher.hashAsync(largeData, (progress) => {
    console.log(`Progress: ${progress.percentComplete.toFixed(1)}%`);
  });
  console.log('Async hash:', hash);

  // Verification
  const isValid = hasher.verify('test', 'expected_hash');
  console.log('Valid:', isValid);

  // HMAC
  const hmac = hasher.createHMAC('secret', 'message');
  console.log('HMAC:', hmac);

  // Multiple hashes
  const hashes = await hasher.hashMultiple(['a', 'b', 'c']);
  console.log('Multiple:', hashes);
}

export {
  TypedSHA224Hasher,
  HashResult,
  HashOptions,
  HashInput,
  HashError,
  createHasher
};

8. Testing Strategies

Comprehensive Test Suite with Jest

JavaScript - Jest Tests
/**
 * SHA-224 Test Suite using Jest
 */

const crypto = require('crypto');
const { SHA224Hasher } = require('./sha224-hasher');

describe('SHA224 Implementation', () => {
  let hasher;

  beforeEach(() => {
    hasher = new SHA224Hasher();
  });

  describe('Basic Functionality', () => {
    test('should hash empty string correctly', () => {
      const result = hasher.hashString('');
      expect(result).toBe('d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f');
    });

    test('should hash known test vectors', () => {
      const testVectors = [
        {
          input: 'abc',
          expected: '23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7'
        },
        {
          input: 'The quick brown fox jumps over the lazy dog',
          expected: '730e109bd7a8a32b1cb9d9a09aa2325d2430587ddbc0c38bad911525'
        }
      ];

      testVectors.forEach(({ input, expected }) => {
        expect(hasher.hashString(input)).toBe(expected);
      });
    });

    test('should produce consistent results', () => {
      const input = 'consistent test';
      const hash1 = hasher.hashString(input);
      const hash2 = hasher.hashString(input);

      expect(hash1).toBe(hash2);
    });
  });

  describe('Input Types', () => {
    test('should handle string input', () => {
      const result = hasher.hashString('test string');
      expect(result).toHaveLength(56);
      expect(result).toMatch(/^[a-f0-9]{56}$/);
    });

    test('should handle buffer input', () => {
      const buffer = Buffer.from('test buffer');
      const result = hasher.hashBuffer(buffer);
      expect(result).toHaveLength(56);
    });

    test('should handle Uint8Array input', () => {
      const array = new Uint8Array([1, 2, 3, 4, 5]);
      const result = hasher.hashBuffer(Buffer.from(array));
      expect(result).toHaveLength(56);
    });

    test('should handle unicode strings', () => {
      const unicodeStrings = [
        '你好世界',
        'مرحبا بالعالم',
        '🚀🌟✨'
      ];

      unicodeStrings.forEach(str => {
        const result = hasher.hashString(str);
        expect(result).toHaveLength(56);
        expect(result).toMatch(/^[a-f0-9]{56}$/);
      });
    });
  });

  describe('Incremental Hashing', () => {
    test('should support incremental updates', () => {
      const hash1 = crypto.createHash('sha224');
      hash1.update('Hello');
      hash1.update(' ');
      hash1.update('World');
      const result1 = hash1.digest('hex');

      const result2 = hasher.hashString('Hello World');

      expect(result1).toBe(result2);
    });

    test('should handle chunked data correctly', () => {
      const chunks = ['chunk1', 'chunk2', 'chunk3'];
      const combined = chunks.join('');

      const chunkedResult = hasher.hashChunks(chunks);
      const directResult = hasher.hashString(combined);

      expect(chunkedResult).toBe(directResult);
    });
  });

  describe('File Hashing', () => {
    const fs = require('fs');
    const path = require('path');
    const os = require('os');

    test('should hash file correctly', async () => {
      // Create temporary test file
      const tmpDir = os.tmpdir();
      const tmpFile = path.join(tmpDir, 'test-' + Date.now() + '.txt');
      const testContent = 'Test file content for SHA-224';

      fs.writeFileSync(tmpFile, testContent);

      try {
        const fileHash = await hasher.hashFile(tmpFile);
        const directHash = hasher.hashString(testContent);

        expect(fileHash).toBe(directHash);
      } finally {
        // Clean up
        fs.unlinkSync(tmpFile);
      }
    });

    test('should handle large files efficiently', async () => {
      const tmpDir = os.tmpdir();
      const tmpFile = path.join(tmpDir, 'large-' + Date.now() + '.bin');

      // Create 10MB file
      const size = 10 * 1024 * 1024;
      const buffer = Buffer.alloc(size, 'x');
      fs.writeFileSync(tmpFile, buffer);

      try {
        const startTime = Date.now();
        const hash = await hasher.hashFile(tmpFile);
        const duration = Date.now() - startTime;

        expect(hash).toHaveLength(56);
        expect(duration).toBeLessThan(5000); // Should complete within 5 seconds
      } finally {
        fs.unlinkSync(tmpFile);
      }
    });
  });

  describe('Error Handling', () => {
    test('should handle null input gracefully', () => {
      expect(() => hasher.hashString(null)).toThrow();
    });

    test('should handle undefined input gracefully', () => {
      expect(() => hasher.hashString(undefined)).toThrow();
    });

    test('should handle non-existent file', async () => {
      await expect(hasher.hashFile('/non/existent/file.txt'))
        .rejects
        .toThrow();
    });

    test('should handle invalid encoding', () => {
      expect(() => {
        const hash = crypto.createHash('sha224');
        hash.update('test', 'invalid-encoding');
      }).toThrow();
    });
  });

  describe('Performance', () => {
    test('should hash 1MB in reasonable time', () => {
      const data = Buffer.alloc(1048576, 'x');
      const iterations = 100;

      const startTime = Date.now();
      for (let i = 0; i < iterations; i++) {
        hasher.hashBuffer(data);
      }
      const duration = Date.now() - startTime;

      const throughput = (iterations * 1048576) / (duration / 1000) / 1048576; // MB/s

      expect(throughput).toBeGreaterThan(10); // At least 10 MB/s
    });
  });

  describe('HMAC', () => {
    test('should create valid HMAC-SHA224', () => {
      const key = 'secret-key';
      const message = 'message to authenticate';

      const hmac = hasher.createHMAC(key, message);

      // Verify against Node's crypto
      const expected = crypto
        .createHmac('sha224', key)
        .update(message)
        .digest('hex');

      expect(hmac).toBe(expected);
    });
  });

  describe('Comparison and Verification', () => {
    test('should verify matching hashes', () => {
      const data = 'test data';
      const hash = hasher.hashString(data);

      const isValid = hasher.verify(data, hash);
      expect(isValid).toBe(true);
    });

    test('should reject non-matching hashes', () => {
      const data = 'test data';
      const wrongHash = 'a'.repeat(56);

      const isValid = hasher.verify(data, wrongHash);
      expect(isValid).toBe(false);
    });

    test('should use constant-time comparison', () => {
      const hash1 = 'a'.repeat(56);
      const hash2 = 'b'.repeat(56);

      const measurements = [];

      for (let i = 0; i < 1000; i++) {
        const start = process.hrtime.bigint();
        hasher.constantTimeCompare(hash1, hash2);
        const end = process.hrtime.bigint();
        measurements.push(Number(end - start));
      }

      // Check that timing is consistent (low variance)
      const mean = measurements.reduce((a, b) => a + b) / measurements.length;
      const variance = measurements.reduce((sum, val) => {
        return sum + Math.pow(val - mean, 2);
      }, 0) / measurements.length;

      const stdDev = Math.sqrt(variance);
      const cv = stdDev / mean; // Coefficient of variation

      expect(cv).toBeLessThan(0.5); // Low variance indicates constant time
    });
  });
});

// Run tests
if (require.main === module) {
  const jest = require('jest');
  jest.run();
}

JavaScript SHA-224 Best Practices

✅ DO:

  • Use Node.js crypto module when available
  • Prefer WebCrypto API in browsers for better performance
  • Stream large files instead of loading into memory
  • Use Web Workers for CPU-intensive operations in browsers
  • Implement proper error handling with try-catch blocks
  • Use TypeScript for type safety in large projects
  • Test with known test vectors
  • Use constant-time comparison for security

❌ DON'T:

  • Block the main thread with synchronous operations in browsers
  • Load entire large files into memory
  • Use string concatenation for binary data
  • Ignore encoding issues (always specify UTF-8)
  • Implement your own crypto unless necessary
  • Trust user input without validation

Performance Tips:

  • Use streaming for files larger than 10MB
  • Leverage Web Workers for parallel processing
  • Use ArrayBuffer/Uint8Array for binary data
  • Cache results when appropriate
  • Profile your code with Chrome DevTools
  • Consider WebAssembly for extreme performance needs

Additional Resources

Node.js Crypto Docs

Official Node.js cryptography module documentation

View Docs

MDN Web Crypto API

Browser Web Crypto API documentation

Learn More

CryptoJS Library

Popular JavaScript cryptography library

Explore