How to Build a Client-Side Hash Calculator with Web Crypto API and MD5

Last week, I was debugging an issue where a file upload kept failing silently. After hours of digging, I realized the problem: the file was corrupted during transfer, but nothing was catching it. I needed to verify file integrity, and the quickest way was comparing hashes.

The problem? I didn’t have a hash tool installed, and uploading sensitive files to random online services felt wrong. Classic “I should just build this myself” moment. Because apparently I enjoy reinventing wheels.

The Web Crypto API: Almost Perfect, Almost

Here’s the thing about modern browsers: they have this beautiful API called crypto.subtle.digest() that handles SHA-1, SHA-256, SHA-384, and SHA-512 natively. It’s fast, secure, and requires zero dependencies.

const hashBuffer = await crypto.subtle.digest('SHA-256', data);

Simple, right? But there’s a catch: MD5 is not supported. Web Crypto API deliberately excludes it because MD5 is cryptographically broken. Fair enough, but sometimes you need MD5 for legacy systems, checksums, or just comparing against old database records.

So I faced a decision: use a library like crypto-js or implement MD5 from scratch.

The Library vs. Hand-Rolled Dilemma

Libraries are great until they’re not. For a single-page tool, pulling in a library means:

  • Extra HTTP requests
  • Potential supply chain risks
  • Bundle size bloat
  • Fighting with module systems

The alternative? Implementing MD5 in vanilla JavaScript. The algorithm is well-documented (RFC 1321), and honestly, it’s only about 100 lines of carefully crafted bit manipulation.

function md5(string) {
  // The classic algorithm: padding, preprocessing, 
  // 64 rounds of F, G, H, I functions with a 512-bit block
  // Returns hex string
}

Was it worth it? For this specific use case, absolutely. The tool runs entirely client-side, no network requests, no dependencies. Everything happens in the browser’s sandbox.

File Hashing: The Memory Trap

Next challenge: file hashing. The naive approach is reading the entire file into memory and hashing it. That works for small files, but try hashing a 2GB video file that way and watch your browser crash.

The solution is chunked reading with FileReader:

async function hashFile(file, algorithm) {
  const chunkSize = 1024 * 1024; // 1MB chunks
  let offset = 0;

  while (offset < file.size) {
    const chunk = file.slice(offset, offset + chunkSize);
    const buffer = await chunk.arrayBuffer();
    // Feed chunk to hasher
    offset += chunkSize;
  }
}

For SHA algorithms, crypto.subtle handles this elegantly with its incremental interface. MD5 was trickier — I had to implement the streaming logic manually, maintaining the internal state between chunks.

The Real-Time Problem

Here’s where things got interesting. I wanted real-time updates as users type. But hashing on every keystroke? That’s a performance nightmare, especially for large inputs.

The solution was a simple debounce pattern:

let timer;
input.addEventListener('input', () => {
  clearTimeout(timer);
  timer = setTimeout(computeHashes, 150);
});

This gives a smooth experience — 150ms debounce feels instant to users but prevents the browser from choking on rapid input.

AI-Assisted Development: The Honest Take

I built this tool with heavy AI assistance, and it was a mixed bag. Here’s how it actually went down.

What AI did well:

  • Generated the initial MD5 implementation correctly (I verified against known test vectors)
  • Handled the boilerplate for file reading and chunking
  • Produced clean, consistent CSS with proper dark mode support

Where AI struggled:
The first version had a subtle bug with the chunked MD5 implementation. The AI didn’t handle the padding correctly when the last chunk wasn’t a full 512-bit block. Classic off-by-one error in the padding logic.

How I fixed it:

I gave the AI a specific prompt: “The MD5 padding logic is wrong. When the file size is an exact multiple of 64 bytes, the padding needs an extra block. Also, the length should be in bits, not bytes.”

After two iterations, it got it right. But I had to understand the algorithm well enough to spot the bug in the first place. AI can write code, but you still need to review it critically.

The Compare Feature: User Error Prevention

A feature I almost skipped but turned out to be valuable: hash comparison. Users often need to verify a checksum against a known value. Instead of making them manually compare hex strings (a terrible idea — eyes glaze over after 20 characters), I added a compare input.

const matches = expectedHash.toLowerCase() === computedHash.toLowerCase();

Case-insensitive comparison because, let’s be honest, everyone pastes hashes with mixed capitalization. The UI shows a clear green “MATCH” or red “NO MATCH” indicator.

Performance Considerations

For large files, I added a progress indicator. The Web Crypto API is fast, but chunked reading and hashing a 10GB file still takes time. Users need feedback or they’ll think the tool is broken.

The progress calculation is straightforward:

const progress = (offset / file.size) * 100;

One thing I learned: crypto.subtle.digest() is surprisingly fast. Even SHA-512 on a 100MB file completes in under a second on modern hardware. The bottleneck is usually the file reading, not the hashing.

Lessons Learned

  1. Web Crypto API is underrated — most developers don’t know it exists. It’s fast, secure, and built into every modern browser.

  2. MD5 implementation is a rabbit hole — even with AI help, I spent time testing edge cases. Known test vectors are essential.

  3. Chunked file processing is non-negotiable — memory limits are real, and users will throw massive files at your tool.

  4. AI assistance changes the game, but not how you’d expect — it’s not about writing code for you; it’s about being a smart pair programmer that catches your blind spots.

The Result

During this process, I built a small browser-based tool to make this workflow easier. It handles text input and file hashing with MD5, SHA-1, SHA-256, SHA-384, and SHA-512, all client-side with zero dependencies.

The real win? No data leaves the browser. For developers dealing with sensitive files, that’s the whole point.

If you’re curious, you can try it here: Hash Calculator

Tags

  1. javascript
  2. webdev
  3. security
  4. cryptography
  5. productivity

Leave a Reply