Table of Contents
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
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