DEV Community

eustatos
eustatos

Posted on • Edited on

Stop Using Heavy MD5 Libraries in 2026: pure-md5 Review

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.

⚠️ v0.2.2 Update: Tree-shaking support added!
• Use import { md5 } from 'pure-md5/md5' for ~1.4KB gzipped
• Full bundle: ~6KB (was <1KB)
• WHATWG streams & improved exports

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 ~1.4KB gz1
Dependencies 0 0 0

1 ~1.4KB gzipped with tree-shaking (md5 only). Full bundle: ~6KB gzipped.

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 }
Enter fullscreen mode Exit fullscreen mode

Tree-Shaking Support (New in v0.2.1+)

Important: pure-md5 now supports modern tree-shaking for smaller bundle sizes. Here's how to use it:

Import Strategies

For minimal bundle (recommended):

import { md5 } from 'pure-md5/md5'; // Only md5() function
// Result: ~1.4KB gzipped
Enter fullscreen mode Exit fullscreen mode

For full API:

import { md5, hashFile, createMD5Stream } from 'pure-md5';
// Result: ~6KB gzipped
Enter fullscreen mode Exit fullscreen mode

Why Tree-Shaking Matters

  • md5() alone: ~500 bytes raw, ~1.4KB gzipped
  • Full library: ~17KB raw, ~6KB gzipped
  • Tree-shaking benefits: Only included code that you actually use

Build Tool Configuration

Most modern bundlers enable tree-shaking by default:

Vite:

// vite.config.js
export default {
  optimizeDeps: {
    include: ['pure-md5/md5'] // Pre-bundle specific entry
  }
};
Enter fullscreen mode Exit fullscreen mode

Webpack:

// webpack.config.js
module.exports = {
  optimization: {
    usedExports: true
  }
};
Enter fullscreen mode Exit fullscreen mode

Rollup:

// rollup.config.js
export default {
  treeshake: true
};
Enter fullscreen mode Exit fullscreen mode

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.

Architecture diagram showing adapter detection flow

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
}
Enter fullscreen mode Exit fullscreen mode

This gives several advantages:

  1. Performance: Uses native C++ code in Node.js (~1.15M ops/sec)
  2. Compatibility: Works in older browsers with JS implementation
  3. 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 });
Enter fullscreen mode Exit fullscreen mode

Node.js Streams (Recommended for Node.js)

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);
Enter fullscreen mode Exit fullscreen mode

WHATWG Streams (Recommended for Browsers)

import { createMD5Stream } from 'pure-md5/stream/whatwg-stream';

const stream = createMD5Stream();
const readable = new ReadableStream({
  start(controller) {
    // Your data source
    controller.enqueue('data');
    controller.close();
  }
});

const result = await readable.pipeThrough(stream).getReader().read();
console.log('MD5:', result.value.digest);
Enter fullscreen mode Exit fullscreen mode

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

No @types/md5, no any. Everything works out of the box.

Export Paths

The library provides multiple entry points for different use cases:

// Main entry (all exports)
import { md5, hashFile, createMD5Stream } from 'pure-md5';

// Tree-shakeable md5 only
import { md5 } from 'pure-md5/md5';

// Adapters
import { NodeCryptoBackend } from 'pure-md5/adapters/node';
import { WebCryptoBackend } from 'pure-md5/adapters/webcrypto';
import { PureJSBackend } from 'pure-md5/adapters/ie11';

// Detection
import { detectAdapter } from 'pure-md5/detect';

// Streaming
import { createMD5Stream } from 'pure-md5/stream';
import { createMD5Stream } from 'pure-md5/stream/whatwg-stream';
Enter fullscreen mode Exit fullscreen mode

Benchmarks

Size and speed are critical metrics for frontend libraries.

Bundle Size (gzipped)

Library Size Dependencies
pure-md5 (md5 only) ~1.4KB 0
pure-md5 (full) ~6KB 0
md5 (pvorb/node-md5) ~3KB 3 (charenc, crypt, is-buffer)
js-md4 ~2KB 0
blueimp-md5 ~2KB 0
crypto-js ~4KB+ 0
spark-md5 ~3KB 0

Note: pure-md5 achieves smaller final sizes through tree-shaking, allowing you to import only what you use.

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 ~1.4KB¹ ~3KB
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
Browser streams ✅ WHATWG streams ❌ None

¹ ~1.4KB with tree-shaking (md5 only). Full bundle: ~6KB.

Usage Examples

// md5 (pvorb) - Node.js only
import md5 from 'md5';

// ❌ Won't work in browser (uses require())
const hash = md5('hello');
Enter fullscreen mode Exit fullscreen mode
// pure-md5 - universal solution
import { md5 } from 'pure-md5/md5'; // Tree-shakeable

// ✅ Works everywhere (Node.js + Browser)
const hash = md5('hello');
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

2. Content-Based Cache Key

import { md5 } from 'pure-md5/md5'; // Tree-shakeable import

function getCacheKey(content) {
  return `cache:${md5(content)}`;
}

const key = getCacheKey(JSON.stringify(userData));
// "cache:5d41402abc4b2a76b9719d911017c592"
Enter fullscreen mode Exit fullscreen mode

3. Universal Hashing (Node + Browser)

// Single file works everywhere
import { md5 } from 'pure-md5/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);
}
Enter fullscreen mode Exit fullscreen mode

4. Streaming API for Large Files (Node.js)

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);
Enter fullscreen mode Exit fullscreen mode

5. Streaming API for Large Files (Browser)

import { createMD5Stream } from 'pure-md5/stream/whatwg-stream';

document.querySelector('#fileInput').addEventListener('change', async (e) => {
  const file = e.target.files[0];
  if (!file) return;

  console.log(`Hashing: ${file.name} (${file.size} bytes)`);

  const stream = createMD5Stream();
  const readable = file.stream();

  const reader = readable.pipeThrough(stream).getReader();
  const result = await reader.read();

  console.log('✅ MD5:', result.value.digest);
  console.log('📦 Size:', result.value.bytesProcessed, 'bytes');
});
Enter fullscreen mode Exit fullscreen mode

6. Manual Adapter Selection

When you need explicit control over the backend:

import { NodeCryptoBackend, WebCryptoBackend, PureJSBackend } from 'pure-md5';

// Create backend instances
const nodeBackend = new NodeCryptoBackend();
const hash = await nodeBackend.hash('hello');
console.log(hash);

// Force pure JS (for testing or special environments)
const pureBackend = new PureJSBackend();
const hash2 = await pureBackend.hash('hello');
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Basic Usage (Tree-Shakeable)

import { md5 } from 'pure-md5/md5';

// String input
const hash = md5('hello');
// "5d41402abc4b2a76b9719d911017c592"
Enter fullscreen mode Exit fullscreen mode

Full API Usage

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);
Enter fullscreen mode Exit fullscreen mode

CDN Usage

<!-- Minimal (md5 only) -->
<script src="https://unpkg.com/pure-md5@latest/md5.js"></script>
<script>
  console.log(md5('hello')); // "5d41402abc4b2a76b9719d911017c592"
</script>

<!-- Full bundle -->
<script src="https://unpkg.com/pure-md5@latest/dist/index.js"></script>
<script>
  console.log(md5('hello')); // "5d41402abc4b2a76b9719d911017c592"
</script>
Enter fullscreen mode Exit fullscreen mode

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:

Top comments (0)