DEV Community

jiebang-tools
jiebang-tools

Posted on

Hashing in JavaScript: A Practical Guide to MD5, SHA-256, and SHA-512

Hashing in JavaScript: A Practical Guide to MD5, SHA-256, and SHA-512

Hashing is everywhere in software development — file integrity checks, password storage, digital signatures, data deduplication. But many developers only know how to "call a library" and run into trouble when implementing it in the browser.

This guide covers practical JavaScript implementations of common hash algorithms, including browser-native APIs, Node.js methods, file hashing, and real-world gotchas.

1. Browser-Native: SubtleCrypto

Modern browsers ship with the Web Crypto API, and SubtleCrypto lets you compute SHA hashes without any third-party library.

Basic Usage

async function sha256(message) {
  const msgBuffer = new TextEncoder().encode(message);
  const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

sha256('Hello World').then(hash => console.log(hash));
Enter fullscreen mode Exit fullscreen mode

Supported Algorithms

await crypto.subtle.digest('SHA-1', data);    // Deprecated
await crypto.subtle.digest('SHA-256', data);  // Most common
await crypto.subtle.digest('SHA-384', data);
await crypto.subtle.digest('SHA-512', data);
Enter fullscreen mode Exit fullscreen mode

⚠️ Note: SubtleCrypto supports SHA family only. For MD5, you'll need a third-party library.

2. File Hashing in the Browser

async function hashFile(file, algorithm = 'SHA-256') {
  const buffer = await file.arrayBuffer();
  const hashBuffer = await crypto.subtle.digest(algorithm, buffer);
  return Array.from(new Uint8Array(hashBuffer))
    .map(b => b.toString(16).padStart(2, '0')).join('');
}
Enter fullscreen mode Exit fullscreen mode

Large File Chunked Hashing

For files over 100MB, use chunked reading to avoid freezing the browser:

async function hashLargeFile(file, algorithm = 'SHA-256', chunkSize = 10 * 1024 * 1024) {
  const chunks = [];
  let offset = 0;
  while (offset < file.size) {
    const chunk = file.slice(offset, offset + chunkSize);
    chunks.push(new Uint8Array(await chunk.arrayBuffer()));
    offset += chunkSize;
  }
  const totalLength = chunks.reduce((acc, c) => acc + c.length, 0);
  const merged = new Uint8Array(totalLength);
  let pos = 0;
  for (const chunk of chunks) { merged.set(chunk, pos); pos += chunk.length; }
  const hashBuffer = await crypto.subtle.digest(algorithm, merged);
  return Array.from(new Uint8Array(hashBuffer))
    .map(b => b.toString(16).padStart(2, '0')).join('');
}
Enter fullscreen mode Exit fullscreen mode

💡 crypto.subtle.digest() does NOT support incremental updates. For true incremental hashing, use spark-md5.

3. MD5 Implementation

Browsers don't natively support MD5, but many legacy systems still use it. spark-md5 supports incremental computation:

import SparkMD5 from 'spark-md5';

function md5String(str) { return SparkMD5.hash(str); }

async function md5File(file, chunkSize = 2 * 1024 * 1024) {
  const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
  const chunks = Math.ceil(file.size / chunkSize);
  let currentChunk = 0;
  const spark = new SparkMD5.ArrayBuffer();
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = (e) => {
      spark.append(e.target.result);
      currentChunk++;
      if (currentChunk < chunks) loadNext();
      else resolve(spark.end());
    };
    reader.onerror = reject;
    function loadNext() {
      const start = currentChunk * chunkSize;
      reader.readAsArrayBuffer(blobSlice.call(file, start, Math.min(start + chunkSize, file.size)));
    }
    loadNext();
  });
}
Enter fullscreen mode Exit fullscreen mode

4. Hashing in Node.js

const crypto = require('crypto');

function sha256(str) { return crypto.createHash('sha256').update(str, 'utf8').digest('hex'); }
function md5(str) { return crypto.createHash('md5').update(str, 'utf8').digest('hex'); }

// File hash with streams
const fs = require('fs');
function hashFileStream(filePath, algorithm = 'sha256') {
  return new Promise((resolve, reject) => {
    const hash = crypto.createHash(algorithm);
    const stream = fs.createReadStream(filePath);
    stream.on('data', chunk => hash.update(chunk));
    stream.on('end', () => resolve(hash.digest('hex')));
    stream.on('error', reject);
  });
}
Enter fullscreen mode Exit fullscreen mode

5. HMAC: Keyed-Hash Message Authentication Code

async function hmacSha256(message, secret) {
  const encoder = new TextEncoder();
  const key = await crypto.subtle.importKey('raw', encoder.encode(secret),
    { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
  const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(message));
  return Array.from(new Uint8Array(signature))
    .map(b => b.toString(16).padStart(2, '0')).join('');
}

// Node.js
function hmacSha256Node(message, secret) {
  return crypto.createHmac('sha256', secret).update(message, 'utf8').digest('hex');
}
Enter fullscreen mode Exit fullscreen mode

6. Common Pitfalls

Encoding Issues

// ❌ Wrong: passing a string directly to digest()
// ✅ Correct: encode with TextEncoder first
const data = new TextEncoder().encode('Hello');
const hash = await crypto.subtle.digest('SHA-256', data);
Enter fullscreen mode Exit fullscreen mode

Case Sensitivity

function compareHash(h1, h2) { return h1.toLowerCase() === h2.toLowerCase(); }
Enter fullscreen mode Exit fullscreen mode

SubtleCrypto Requires Secure Context

crypto.subtle is only available under HTTPS or localhost.

Never Hash Passwords Directly

MD5/SHA-256 of passwords are vulnerable. Always use salt + slow hash:

async function hashPassword(password, salt) {
  const keyMaterial = await crypto.subtle.importKey('raw',
    new TextEncoder().encode(password), 'PBKDF2', false, ['deriveBits']);
  return crypto.subtle.deriveBits({
    name: 'PBKDF2', salt: new TextEncoder().encode(salt),
    iterations: 100000, hash: 'SHA-256'
  }, keyMaterial, 256);
}
Enter fullscreen mode Exit fullscreen mode

For production, use bcrypt, scrypt, or Argon2.

7. Algorithm Selection Guide

Use Case Recommended Algorithm
File integrity check SHA-256
Password storage bcrypt/Argon2
Digital signatures SHA-256 + RSA
Data dedup/cache keys MD5/SHA-1
HMAC signatures HMAC-SHA256
Blockchain SHA-256
Legacy compatibility MD5 (non-security only)

Tool Recommendation

If you need to quickly compute hashes for text or files, try the free online hash calculator — supports MD5, SHA-1, SHA-256, SHA-384, and SHA-512. All computation happens in your browser; no data is uploaded to any server.

Source code: github.com/jiebang-tools/tools

Summary

  1. Use crypto.subtle.digest() in browsers for SHA — no libraries needed
  2. MD5 requires a third-party library (spark-md5) or Node.js built-in crypto
  3. Use chunked + incremental hashing for large files
  4. SubtleCrypto requires HTTPS (except localhost)
  5. Never store passwords with plain hashes — use bcrypt/Argon2
  6. Use HMAC for message authentication

Happy coding! 🚀

Top comments (0)