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));
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);
⚠️ 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('');
}
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('');
}
💡
crypto.subtle.digest()does NOT support incremental updates. For true incremental hashing, usespark-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();
});
}
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);
});
}
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');
}
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);
Case Sensitivity
function compareHash(h1, h2) { return h1.toLowerCase() === h2.toLowerCase(); }
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);
}
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
- Use
crypto.subtle.digest()in browsers for SHA — no libraries needed - MD5 requires a third-party library (spark-md5) or Node.js built-in crypto
- Use chunked + incremental hashing for large files
- SubtleCrypto requires HTTPS (except localhost)
- Never store passwords with plain hashes — use bcrypt/Argon2
- Use HMAC for message authentication
Happy coding! 🚀
Top comments (0)