Every developer uses online file and text utilities daily: PDF metadata scrubbers, photo EXIF strippers, AES-256 encryptors, and prompt cleaners.
However, a serious architectural flaw exists across most legacy web utilities: your sensitive files and tokens get transmitted to a remote backend server for processing. Whether it's an authorization bearer token, a confidential legal PDF, or a photo with embedded GPS coordinates, trusting a third-party server creates unnecessary security liability.
In this guide, we'll explore how modern browser APIs—specifically the Web Cryptography API (SubtleCrypto) and HTML5 Canvas 2D API—allow us to build high-performance, military-grade security utilities that run 100% in client-side RAM with zero server infrastructure.
1. AES-256-GCM Encryption with PBKDF2 (Native Web Crypto)
Instead of pulling heavy NPM packages like CryptoJS, modern browsers ship with hardware-accelerated cryptographic primitives via window.crypto.subtle.
Here is a production-ready TypeScript implementation that derives a 256-bit key using PBKDF2 with 250,000 SHA-256 iterations and encrypts plaintext using AES-GCM (Authenticated Encryption with Associated Data):
export async function encryptClientSide(plaintext: string, passphrase: string): Promise<string> {
const enc = new TextEncoder();
const salt = window.crypto.getRandomValues(new Uint8Array(16));
const iv = window.crypto.getRandomValues(new Uint8Array(12)); // 96-bit IV for GCM
// 1. Import raw passphrase into CryptoKey
const keyMaterial = await window.crypto.subtle.importKey(
'raw',
enc.encode(passphrase),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
// 2. Derive strong AES-256 key with 250,000 PBKDF2 iterations
const key = await window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 250000,
hash: 'SHA-256',
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
// 3. Encrypt payload
const encryptedBuffer = await window.crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
enc.encode(plaintext)
);
// 4. Pack Salt + IV + Ciphertext into single Base64 bundle
const combined = new Uint8Array(salt.length + iv.length + encryptedBuffer.byteLength);
combined.set(salt, 0);
combined.set(iv, salt.length);
combined.set(new Uint8Array(encryptedBuffer), salt.length + iv.length);
return btoa(String.fromCharCode(...combined));
}
Why AES-GCM Over AES-CBC?
AES-GCM is an AEAD cipher that includes built-in tamper detection (128-bit authentication tag). If a single bit in the ciphertext is altered or if the decryption passphrase is incorrect, crypto.subtle.decrypt() automatically throws a cryptographic exception. This eliminates the need for custom HMAC verification and protects against padding oracle vulnerabilities.
2. In-Browser Forensic EXIF Stripping via Canvas Rasterization
Removing GPS coordinates, camera serial numbers, and thumbnail previews from JPEG/PNG images can be done without server-side ImageMagick or Node.js backends.
By drawing the image onto an offscreen HTML5 <canvas> element and re-encoding it into a fresh raster blob, all ancillary EXIF, TIFF, and XMP metadata blocks are stripped cleanly in browser RAM:
export async function stripExifClientSide(imageFile: File): Promise<Blob> {
return new Promise((resolve, reject) => {
const img = new Image();
const objectUrl = URL.createObjectURL(imageFile);
img.onload = () => {
URL.revokeObjectURL(objectUrl);
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d');
if (!ctx) return reject(new Error('Canvas 2D context unavailable'));
ctx.drawImage(img, 0, 0);
canvas.toBlob(
(blob) => {
if (blob) resolve(blob);
else reject(new Error('Blob generation failed'));
},
imageFile.type,
0.95
);
};
img.onerror = () => reject(new Error('Image failed to load'));
img.src = objectUrl;
});
}
3. Two-Way AI Prompt Sanitization (Mask & Restore PII)
When sending internal source code, error logs, or confidential documents to LLMs like ChatGPT or Claude, Personally Identifiable Information (PII) such as emails, IP addresses, and API keys must be intercepted before transmission.
The Two-Way Masking Workflow:
-
Interception: A client-side regex engine replaces sensitive patterns with synthetic tokens (
[EMAIL_1],[IP_1],[API_KEY_1]). -
Local Session Map: A temporary
Map<string, string>stores the mapping strictly in volatile browser memory. - LLM Execution: The sanitized prompt is pasted into ChatGPT without leaking the real identities.
- 1-Click Restore: When ChatGPT returns the response containing the placeholder tokens, the local lookup table swaps the original values back into place.
4. Stripping URL Tracking Query Parameters
Marketing platforms and social networks append tracking parameters (utm_source, fbclid, gclid, mc_eid) to URLs to build invasive cross-site user profiles.
A lightweight client-side URL sanitizer strips all invasive query parameters using the standard URL and URLSearchParams browser APIs:
const TRACKING_PARAMS = new Set([
'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',
'fbclid', 'gclid', 'msclkid', 'dclid', 'twclid', 'mc_eid', '_ga'
]);
export function cleanUrl(rawUrl: string): string {
try {
const parsed = new URL(rawUrl);
for (const key of Array.from(parsed.searchParams.keys())) {
if (TRACKING_PARAMS.has(key.toLowerCase()) || key.startsWith('utm_')) {
parsed.searchParams.delete(key);
}
}
return parsed.toString();
} catch {
return rawUrl;
}
}
🚀 Live Implementation & Open Architecture
If you want to test these utilities live or inspect network requests in Chrome DevTools to verify that zero bytes leave your device, check out the live suite on PrivacyKit.
Key Architectural Benefits for Web Engineers:
- $0 Backend Infrastructure: Serving pure client-side static bundles on Vercel/Cloudflare Pages means infinite scalability with zero server compute costs.
- Instant Offline Execution: Utilities continue to execute without interruption even if you disconnect from Wi-Fi.
- GDPR by Design: No server database means zero risk of data breach liabilities under GDPR Article 5(1)(c).
What client-side tools are you currently building with the Web Crypto API? Let's discuss in the comments below! 👇



Top comments (0)