In 2026, writing a new MD5 library might seem odd. The algorithm is considered outdated, browsers are gradually removing it from Web Crypto API, and Node.js documentation includes warnings about its use.
But reality is more nuanced. MD5 is still very much alive in:
- File checksums (many repositories still publish MD5 hashes)
- Content-based caching (cache keys derived from content)
- Legacy protocols (some APIs still require MD5 signatures)
- Internal identifiers (where security isn’t critical)
The problem? Existing implementations are either outdated, lack TypeScript support, or don’t work universally (Node.js + browser). I decided to fix this and created pure-md5 — a modern, typed, and adaptive library.
In this article, I’ll share the technical decisions, architecture, and show why it might be useful in your projects.
⚠️ Important Security Warning: MD5 is cryptographically insecure. Do not use for passwords, tokens, or digital signatures. Only use for checksums, caching, and compatibility purposes.
Why Not Built-in crypto?
The first question that comes to mind: “Why a new library when Node.js has crypto and browsers have SubtleCrypto?”
| Problem | Node.js crypto
|
Browser SubtleCrypto
|
pure-md5 |
|---|---|---|---|
| Works in browser | ❌ | ✅ | ✅ |
| Works in Node.js | ✅ | ❌ | ✅ |
| TypeScript out of box | ❌ (needs @types) | ⚠️ Limited | ✅ |
| MD5 support | ✅ | ⚠️ (being removed) | ✅ |
| Bundle size | Native | Native | <1KB gz |
| Dependencies | 0 | 0 | 0 |
Key advantage of pure-md5: universality. You write code once, and it works everywhere. The library automatically selects the optimal adapter:
// Same code works in both Node.js and browser
import { md5, hashFile } from 'pure-md5';
const hash = md5('hello');
// "5d41402abc4b2a76b9719d911017c592"
const fileHash = await hashFile('large-file.bin');
// { digest: '...', bytesProcessed: 1048576 }
Architecture: Adapter System
The heart of the library is an adaptive system that tries to use native APIs, falling back to pure JavaScript implementation when needed.
How Detection Works
// Simplified adapter selection logic
function detectAdapter() {
if (typeof process !== 'undefined' && process.versions?.node) {
return nodeAdapter; // Priority 1
}
if (typeof crypto !== 'undefined' && crypto.subtle) {
return webCryptoAdapter; // Priority 2
}
return pureJSAdapter; // Fallback
}
This gives several advantages:
- Performance: Uses native C++ code in Node.js (~1.15M ops/sec)
- Compatibility: Works in older browsers with JS implementation
- Future-proof: Continues working even if WebCrypto removes MD5
Working with Large Files: Streaming & Progress
For files larger than memory, use the streaming API:
import { hashFile } from 'pure-md5';
// Simple file hashing
const result = await hashFile('large-file.bin');
console.log('MD5:', result.digest);
// "5d41402abc4b2a76b9719d911017c592"
console.log('Bytes processed:', result.bytesProcessed);
// 104857600
// With progress tracking
const progress = (percent) => {
console.log(`Progress: ${percent.toFixed(1)}%`);
};
const result = await hashFile('large-file.bin', { onProgress: progress });
Under the Hood
The library uses streaming data processing:
class MD5Stream extends Transform {
constructor() {
super();
this.hash = createMD5Context();
this.bytesProcessed = 0;
}
_transform(chunk, encoding, callback) {
this.hash.update(chunk);
this.bytesProcessed += chunk.length;
this.emit('progress', { bytesProcessed: this.bytesProcessed });
callback();
}
_flush(callback) {
this.emit('md5', {
digest: this.hash.digest('hex'),
bytesProcessed: this.bytesProcessed
});
callback();
}
}
This allows hashing files of any size without memory overflow risk.
TypeScript First
In 2026, a library without types is second-class. pure-md5 is written in TypeScript 5.6+ with full type definitions.
// Full type coverage for all APIs
import { md5, hashFile, MD5Result } from 'pure-md5';
const hash: string = md5('hello');
const result: MD5Result = await hashFile('file.txt');
// result.digest: string
// result.bytesProcessed: number
No @types/md5, no any. Everything works out of the box.
Benchmarks
Size and speed are critical metrics for frontend libraries.
Bundle Size (gzipped)
| Library | Size | Dependencies |
|---|---|---|
| pure-md5 | <1KB | 0 |
| md5 (pvorb/node-md5) | ~6KB | 3 (charenc, crypt, is-buffer) |
| js-md4 | ~2KB | 0 |
| blueimp-md5 | ~2KB | 0 |
| crypto-js | ~4KB+ | 0 |
| spark-md5 | ~3KB | 0 |
Speed (Operations/Second, Node.js)
| Library | ops/sec |
|---|---|
| Node.js crypto | ~1,200,000 |
| pure-md5 (node adapter) | ~1,150,000 |
| pure-md5 (pure JS) | ~450,000 |
| md5 (pvorb) | ~350,000 |
| crypto-js | ~380,000 |
| spark-md5 | ~420,000 |
In Node.js, pure-md5 nearly matches native crypto performance thanks to its adapter. In browsers, the pure JS implementation is still faster than competitors due to optimizations.
Comparison with md5 (pvorb/node-md5)
The md5 package (from pvorb/node-md5) is one of the most popular MD5 implementations (~2.5M weekly downloads). However, it has several limitations:
Key Differences
| Feature | pure-md5 | md5 (pvorb) |
|---|---|---|
| Compatibility | Node.js + Browser | Node.js only |
| TypeScript | ✅ Out of box | ❌ No types |
| Bundle Size | <1KB | ~6KB |
| Dependencies | 0 | 3 (charenc, crypt, is-buffer) |
| Tree-shaking | ✅ Supported | ❌ CommonJS only |
| Stream API | ✅ Built-in | ❌ None |
| Adapters | Auto-detection | One-size-fits-all |
| Performance | ~1.15M ops/sec | ~350K ops/sec |
Usage Examples
// md5 (pvorb) - Node.js only
import md5 from 'md5';
// ❌ Won't work in browser (uses require())
const hash = md5('hello');
// pure-md5 - universal solution
import { md5 } from 'pure-md5';
// ✅ Works everywhere
const hash = md5('hello');
// Node.js: ✅
// Browser: ✅
Why is md5 Slower?
The md5 package uses byte-by-byte string processing and auxiliary libraries for encoding:
// From md5 (pvorb) - inefficient approach
const str = new String(string);
const bytes = [];
for (let i = 0; i < str.length; i++) {
bytes.push(str.charCodeAt(i));
}
// ... then process byte array
pure-md5 uses a more efficient approach — working with 32-bit words and optimized loops.
Practical Usage Examples
1. Verify Downloaded File Integrity
import { verifyFile } from 'pure-md5';
const isVerified = await verifyFile(
'downloaded-file.zip',
'5d41402abc4b2a76b9719d911017c592' // Expected hash from server
);
if (!isVerified) {
throw new Error('File corrupted during download');
}
// Returns true or false
2. Content-Based Cache Key
import { md5 } from 'pure-md5';
function getCacheKey(content) {
return `cache:${md5(content)}`;
}
const key = getCacheKey(JSON.stringify(userData));
// "cache:5d41402abc4b2a76b9719d911017c592"
3. Universal Hashing (Node + Browser)
// Single file works everywhere
import { md5 } from 'pure-md5';
export function generateId(data) {
if (typeof data === 'string') {
return md5(data);
}
// For binary data (ArrayBuffer/Uint8Array)
if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
// Convert to string for md5
return md5(new TextDecoder().decode(data));
}
// For Buffer (Node.js)
return md5(data);
}
4. Streaming API for Large Files
import { createMD5Stream } from 'pure-md5';
import fs from 'fs';
const stream = createMD5Stream();
stream.on('md5', (result) => {
console.log('MD5:', result.digest);
console.log('Bytes:', result.bytesProcessed);
});
fs.createReadStream('large-file.bin').pipe(stream);
Security: Where to Use and Where Not To
| Scenario | Use MD5? | Alternative |
|---|---|---|
| File checksums | ✅ | — |
| Cache keys | ✅ | — |
| Internal object IDs | ✅ | — |
| User passwords | ❌ | bcrypt, argon2 |
| JWT tokens | ❌ | HS256, RS256 |
| Digital signatures | ❌ | SHA-256, EdDSA |
Rule: If an attacker could benefit from hash manipulation — don’t use MD5.
Installation & Quick Start
npm install pure-md5
# or
yarn add pure-md5
# or
pnpm add pure-md5
import { md5, hashFile, createMD5Stream } from 'pure-md5';
// String input
const hash = md5('hello');
// "5d41402abc4b2a76b9719d911017c592"
// File hashing
const result = await hashFile('path/to/file.txt');
// Streaming
const stream = createMD5Stream();
fs.createReadStream('file.bin').pipe(stream);
CDN Usage
console.log(md5('hello')); // "5d41402abc4b2a76b9719d911017c592"
Conclusion
pure-md5 doesn’t try to revive MD5 for security purposes. It’s a tool for specific tasks where MD5 is still used, but the implementation should be modern, typed, and universal.
If you need file checksums, cache keys, or legacy API compatibility — pure-md5 gives you the best developer experience without extra dependencies.
Links:
