Building Zero-Dependency Secret Sharing with Browser-Native Cryptography

Jul 18, 2026 web-crypto end-to-end-encryption javascript security cryptography secret-sharing browser-api

The main blog post in markdown format

Let's be honest: sharing sensitive information online is a mess. You email a password, then frantically delete it. You use a third-party service, hoping they're not reading your data. You Slack it, knowing your company has that archive forever. There's got to be a better way.

Turns out, there is—and you can build it entirely in the browser.

The Problem with Third-Party Secret Sharing

Services like OneTimeSecret pioneered the concept of self-destructing links. You paste a secret, get a one-time link, share it, and poof—gone after reading. But there's a catch that most users don't think about: what's actually happening to your data?

Even with "temporary" services, your plaintext secret often touches their servers. They promise they don't read it. They promise they delete it. But why trust promises when you can eliminate the possibility entirely?

This is where end-to-end encryption (E2EE) changes everything. With true E2EE, the server cannot read your secret. It only stores ciphertext—useless random bytes without the key. The server becomes a dumb storage box, not a guardian of your secrets.

How E2EE Secret Sharing Actually Works

Imagine Bob wants to share a database password with Alice. Here's the beautiful part: the encryption happens entirely in Bob's browser before anything touches the server.

The flow:

  1. Bob types his secret and chooses a passphrase
  2. His browser derives a cryptographic key from that passphrase
  3. The secret gets encrypted locally using that key
  4. Bob sends Alice a link (containing the ciphertext and salt) plus the passphrase (via a separate channel)

Why separate channels? If both the link and passphrase travel together and someone intercepts the link, they get everything. Send the link via Slack; the passphrase via Signal. Or SMS. Or carrier pigeon. The point is: two different attack surfaces.

Alice receives the link, enters the passphrase her browser locally, the key gets derived again, and the secret decrypts—all without the server ever seeing readable data.

The Cryptography Behind the Magic

Here's where it gets technical, but stick with me—it's elegant.

Key Derivation: PBKDF2

Humans are terrible at generating random, high-entropy keys. We type "password123" and feel creative. So we need a function that transforms weak passphrases into strong cryptographic keys—slowly, on purpose.

PBKDF2 (Password-Based Key Derivation Function 2) does exactly this. It takes your passphrase, adds a random salt (stored publicly), and runs it through 600,000 iterations of SHA-256 hashing. Each guess in a brute-force attack costs CPU time. At 600k iterations, cracking a mediocre passphrase becomes computationally infeasible.

async function deriveKey(passphrase, salt) {
  const keyMaterial = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(passphrase),
    "PBKDF2",
    false,
    ["deriveKey"]
  );
  
  return crypto.subtle.deriveKey(
    {
      name: "PBKDF2",
      salt: salt,
      iterations: 600_000,
      hash: "SHA-256"
    },
    keyMaterial,
    { name: "AES-GCM", length: 256 },
    false,
    ["encrypt", "decrypt"]
  );
}

Symmetric Encryption: AES-GCM

For the actual encryption, we use AES-256-GCM. This isn't the AES you might have heard about in theoretical contexts—it's the authenticated version. GCM mode not only encrypts your data but also generates an authentication tag. If anyone tampers with the ciphertext in transit, decryption fails with an error rather than silently producing garbage.

It also requires a unique initialization vector (IV) for each encryption. Think of it like the salt: public, random, and essential for security. AES-GCM is solid—it's what the NSA recommends for classified information.

async function encrypt(plaintext, key) {
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encoded = new TextEncoder().encode(plaintext);
  
  const ciphertext = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv: iv },
    key,
    encoded
  );
  
  return { iv, ciphertext };
}

Why Zero Dependencies Matters

This approach is radical: no npm packages. No crypto libraries. No trust in third-party code.

You're using what's already in your browser. Web Crypto API has been standardized since 2015 and ships in every modern browser. It's backed by the platform vendors—Apple, Google, Microsoft, Mozilla—who've invested heavily in correct, constant-time implementations.

Consider the attack surface of typical dependency-heavy applications. Log4Shell happened because of a dependency. The left-pad incident broke the internet because of a dependency. Every npm install is a trust decision.

With Web Crypto API, you trust the browser's native implementation. That's a much smaller attack surface than trusting some random npm package with 2 million weekly downloads.

Server Side: Dumb Storage is Good Storage

On the backend, you store:

  • The ciphertext (useless without the key)
  • The salt (not secret, needed for key derivation)
  • The IV (not secret, needed for decryption)
  • Metadata (creation time, expiration, view count)

Configure your Redis (or Valkey) with save "" and appendonly no—meaning no persistence whatsoever. When the process dies, everything evaporates. Set up proper swap configuration to prevent any data hitting disk.

The server becomes a temporary encrypted blob store. It enforces access rules (one-time view, expiration) but cannot read contents even if it wanted to.

What This Means for Your Stack

You can embed this directly in any web application. A form field here, a few JavaScript functions there, and suddenly your app has secure, ephemeral secret sharing built-in. No backend changes required—your server just stores and serves encrypted blobs.

This is particularly powerful for:

  • Internal tools that need to share credentials temporarily
  • Customer support systems transferring sensitive data
  • Developer workflows sharing API keys or database passwords

The secrets are cryptographically isolated from your infrastructure. Even if someone compromises your database, they get ciphertext they cannot crack without the passphrase.

The Tradeoffs Are Worth It

Is PBKDF2 with 600k iterations slower than Argon2 would be? Yes. Argon2, the modern gold standard for password hashing, isn't available in Web Crypto API yet. But consider the context: these are one-time secrets, usually viewed within minutes. The computational cost is acceptable.

The tradeoff—slightly weaker KDF for zero dependencies—is worth it for most use cases. You're not protecting nuclear launch codes. You're sharing a database password that will be changed tomorrow anyway.

Build It, Don't Trust It

The beauty of E2EE is that you don't need to trust the service operator. You can audit the code. You can self-host it. You can verify that the server truly never sees plaintext.

In a world where data breaches are daily news, building systems where the server is cryptographically blind isn't just clever—it's responsible.

So the next time you need to share a secret online, remember: the best way to protect data is to never let it leave the user's hands unencrypted. And with Web Crypto API, you have the tools to make that happen, right in the browser.


Ready to implement? The Web Crypto API is available in all modern browsers. No install, no build step, no dependencies—just pure browser-native cryptography waiting to be used.

Read in other languages:

FR ES DE DA ZH-HANS