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 { 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 Web Crypto API is underrated - most developers don't know it exists. It's fast, secure, and built into every modern browser. MD5 implementation is a rabbit hole - even with AI help, I spent time testing edge cases. Known test vectors are essential. Chunked file processing is non-negotiable - memory limits are real, and users will throw massive files at your tool. 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 javascript webdev security cryptography productivity Top comments (0)
Comments
No comments yet. Start the discussion.