DEV Community

gofortool
gofortool

Posted on

I Built 19 Browser-Based Security Tools Using Only Client-Side JavaScript, Here's What I Learned

Last year I got frustrated.

I needed to encrypt a quick note to send over email. Every tool I found either wanted my email address, uploaded my text to their server, or had a free tier that barely worked. For an encryption tool. The irony wasn't lost on me.

So I started building. What was supposed to be one tool turned into 19. A password generator, AES-256 encryption, EXIF metadata remover, SHA-256 hash calculator, browser fingerprint test, JWT decoder, and more - all running entirely in the browser.

No server. No signup. No database. Just JavaScript and the Web Crypto API.

Here's what I learned building CyberShield Hub.

The Web Crypto API Is Surprisingly Powerful

Most developers don't realize that modern browsers ship with a full cryptographic library. The Web Crypto API gives you:

  • crypto.getRandomValues() for cryptographically secure random numbers
  • crypto.subtle.encrypt() / decrypt() for AES-GCM, AES-CBC, RSA
  • crypto.subtle.digest() for SHA-256, SHA-384, SHA-512
  • crypto.subtle.generateKey() for key generation

For the password generator, I use crypto.getRandomValues() to fill a Uint32Array and map values to character sets. This is the same CSPRNG that banking apps use — not Math.random().

function generatePassword(length, charset) {
    const array = new Uint32Array(length);
    crypto.getRandomValues(array);
    return Array.from(array, (val) => charset[val % charset.length]).join('');
}
Enter fullscreen mode Exit fullscreen mode

Simple. Secure. Zero dependencies.

AES-256 in the Browser - The Tricky Parts

The encryption tool uses AES-GCM (Galois/Counter Mode) which provides both confidentiality and authentication. The flow:

  1. User enters text + password
  2. Derive a key from password using PBKDF2 (100,000 iterations)
  3. Generate a random IV using crypto.getRandomValues()
  4. Encrypt with AES-256-GCM
  5. Return Base64-encoded result (salt + IV + ciphertext)

The tricky part? Key derivation. You can't just use the password directly as an AES key. PBKDF2 stretches it into a proper 256-bit key, making brute-force attacks on weak passwords much harder.

const keyMaterial = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(password),
    'PBKDF2',
    false,
    ['deriveKey']
);

const key = await crypto.subtle.deriveKey(
    { name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' },
    keyMaterial,
    { name: 'AES-GCM', length: 256 },
    false,
    ['encrypt', 'decrypt']
);
Enter fullscreen mode Exit fullscreen mode

EXIF Removal Without a Server

This one was interesting. EXIF data is embedded in JPEG files following the JFIF/EXIF specification. To strip it client-side:

  1. Read the file as an ArrayBuffer using FileReader
  2. Parse the JPEG markers (they start with 0xFF)
  3. Identify and remove APP1 markers (0xFFE1) which contain EXIF data
  4. Reconstruct the file without those markers
  5. Create a new Blob for download

The alternative approach - drawing to a Canvas element and exporting - works but can reduce quality slightly due to re-compression. Direct marker removal preserves the original image data.

Browser Fingerprinting: The Uncomfortable Truth

Building the fingerprint test tool was eye-opening. I'm collecting the same signals that trackers use:

  • Canvas rendering (draw text, read back pixel data - different GPUs produce different results)
  • WebGL renderer string (exposes your exact GPU model)
  • Installed fonts (via canvas measurement technique)
  • Audio processing signature (AudioContext oscillator)
  • Screen properties, timezone, language, plugins

Combining these produces a hash that's unique for roughly 1 in 500,000 browsers. The uncomfortable part? There's not much users can do about it. Even installing privacy extensions can make you more unique.

What I'd Do Differently

Performance. Some tools load dependencies they don't need. I should have code-split more aggressively. The CyberShield Hub loads all 19 tools upfront - lazy loading per tool would improve initial load time.

Testing. Client-side crypto is hard to test because you can't mock crypto.subtle easily. I ended up writing integration tests that actually encrypt/decrypt and verify round-trip correctness.

Documentation. I should have documented the security model earlier. Users rightfully want to verify that "client-side only" claims are true. Showing them the Network tab in DevTools helps, but an architectural doc would be better.

The Tools

Everything is free, no signup, open to anyone:

If you're building client-side security tools, I'm happy to answer questions in the comments. And if you have ideas for tool #20, I'm all ears.

Top comments (0)