When you upload a file to most cloud services, here's what actually happens: your file travels to their servers in plaintext (or gets decrypted on their end), and they hold the encryption keys. That means they can read your data. Whether they do is a different question, but architecturally, you've transferred trust.
There's a better model. It's called zero-knowledge encryption, and it flips the architecture entirely.
The Problem With Server-Side Encryption
Services like Dropbox, Google Drive, and iCloud offer "encryption at rest." This sounds safe, but it means:
- Your file is encrypted after reaching their servers
- They hold the keys
- Any employee, court order, or data breach can expose your files
"Encryption at rest" is really just protection against someone stealing their hard drives. It doesn't protect you from the service itself.
The Zero-Knowledge Alternative
Zero-knowledge means the server mathematically cannot read your data.
Here's the pattern:
Client side:
1. Generate a random 256-bit AES key
2. Encrypt the file with AES-256-GCM
3. Upload the ciphertext to the server
4. Embed the key in URL fragment: https://yourservice.com/file#KEY_HERE
Server side:
- Receives: encrypted blob only
- Stores: encrypted blob only
- Key: never seen
The URL fragment (the # part) is defined in RFC 3986 as a client-side identifier. Browsers intentionally do not send fragment identifiers in HTTP requests. The key lives in the browser and never touches your server's logs.
Why AES-256-GCM Specifically
GCM (Galois/Counter Mode) is an authenticated encryption mode, meaning it provides:
- Confidentiality: data is encrypted
- Integrity: any tampering is detectable (the auth tag fails)
- Authentication: you know the data hasn't been modified
With AES-GCM, the decryption will fail immediately if the ciphertext was altered. With unauthenticated encryption modes like AES-CBC, you wouldn't know.
Implementing It With the Web Crypto API
The browser's built-in SubtleCrypto API is all you need:
// Generate key
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
// Generate IV (must be unique per encryption operation)
const iv = crypto.getRandomValues(new Uint8Array(12));
// Encrypt
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
fileArrayBuffer
);
// Export key for the URL fragment
const exportedKey = await crypto.subtle.exportKey('raw', key);
const keyHex = Array.from(new Uint8Array(exportedKey))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
Note: Always generate a fresh IV for every encryption. Reusing an IV with the same key completely breaks AES-GCM security.
The Trust Model Difference
| Server-Side Encryption | Zero-Knowledge | |
|---|---|---|
| Who holds keys? | Service provider | Only you |
| Can service read files? | Yes | Never |
| Breach exposes files? | Yes | Encrypted blobs only |
| Subpoena risk | High | None |
Real-World Example
FileShot.io implements this architecture for file sharing. When you upload:
- Your file is encrypted in browser memory using AES-256-GCM
- The ciphertext is uploaded
- The key and IV are embedded in the share URL fragment
- The recipient's browser fetches the ciphertext, extracts the key from the fragment, and decrypts locally
The server's database contains only encrypted blobs. Even with full database access, there's nothing readable.
The Bottom Line
"We encrypt your data" from a service provider means nothing unless you hold the keys. Using a service that implements browser-side encryption means the server's trustworthiness becomes irrelevant.
The Web Crypto API makes this implementable in pure JavaScript with no external dependencies.
Trust math, not promises.
Top comments (0)