Blog/Technical Tutorial

How We Built a Zero-Knowledge Secret Sharing Tool with Cloudflare in 5 Days

Dev Team2025-08-038 minutes
Building a zero-knowledge secret sharing tool with Cloudflare

In an age where digital privacy feels like a relic of the past, we embarked on an ambitious mission: build a secret sharing platform that could guarantee complete anonymity and zero-knowledge security in just five days. The challenge wasn't just technical—it was philosophical. How do you create a service that helps people share sensitive information while ensuring that the service itself becomes a digital black hole, incapable of retaining or accessing any user data? This is the behind-the-scenes story of VanishingVault's creation, a technical sprint that proved privacy-first architecture isn't just possible, it's practical. Using Cloudflare's edge infrastructure, we built a system where secrets truly vanish, leaving no trace of their existence once viewed.

Technical Implementation

When we began architecting our zero-knowledge secret sharing platform, we faced a fundamental challenge that has plagued security engineers for decades: how do you build a system that's simultaneously ultra-secure and lightning-fast? Traditional approaches force you to choose between security and performance, but we refused to accept this compromise. After evaluating dozens of cloud platforms and architectural patterns, we discovered that Cloudflare's edge computing infrastructure offered something unique—the ability to process sensitive data at the network edge while maintaining enterprise-grade security guarantees.

The decision to build on Cloudflare Workers wasn't just about technical capabilities; it was about reimagining what's possible when you distribute security processing across a global network. Instead of funneling all traffic through centralized data centers, we could encrypt and decrypt secrets at locations just milliseconds away from our users. This edge-first approach meant that a secret shared in Tokyo would be processed by infrastructure in Asia, while a secret shared in London would be handled by European servers, dramatically reducing latency while maintaining the same security standards globally.

Architecture Overview

Our architecture embodies a radical departure from traditional web application design. Instead of the typical three-tier architecture with presentation, application, and database layers, we created a two-tier system where security operations happen exclusively on the client side. The frontend, built with Next.js and powered by the Web Crypto API, handles all encryption and decryption operations within the user's browser. This means that sensitive data is transformed into cryptographic ciphertext before it ever leaves the user's device.

The backend component—a lightweight Cloudflare Worker—serves as nothing more than an encrypted data courier. It receives opaque, encrypted blobs from clients and stores them in Cloudflare's KV storage system without any ability to decrypt or inspect the contents. This architectural decision creates a perfect security boundary: even if our entire backend infrastructure were compromised, attackers would find only meaningless encrypted data with no way to derive the original secrets.

This zero-knowledge approach represents more than just a security feature—it's a fundamental philosophical stance about data ownership and privacy. By ensuring that decryption keys never touch our servers, we've created a system where users maintain complete control over their sensitive information, even while leveraging our global infrastructure for storage and delivery.

Backend Implementation with Cloudflare Workers

Let's look at how we implemented the core functionality in our Cloudflare Worker:

// Store endpoint - saves an encrypted secret
async function handleStore(request) {
  const { encryptedData, expiresAt } = await request.json();
  
  // Generate a random ID for the secret
  const id = crypto.randomUUID();
  
  // Store the encrypted data in KV with TTL
  await SECRETS_STORE.put(
    id,
    JSON.stringify({ encryptedData }),
    { expirationTtl: 604800 } // 7 days in seconds
  );
  
  return new Response(
    JSON.stringify({ id }),
    { headers: { 'Content-Type': 'application/json' } }
  );
}

// Retrieve endpoint - gets and deletes a secret (one-time access)
async function handleRetrieve(request, id) {
  // Get the secret from KV
  const secretData = await SECRETS_STORE.get(id);
  
  if (!secretData) {
    return new Response(
      JSON.stringify({ error: 'Secret not found or already viewed' }),
      { status: 404, headers: { 'Content-Type': 'application/json' } }
    );
  }
  
  // Delete the secret immediately (one-time access)
  await SECRETS_STORE.delete(id);
  
  return new Response(
    secretData,
    { headers: { 'Content-Type': 'application/json' } }
  );
}

Frontend Encryption with Web Crypto API

On the frontend, we use the Web Crypto API to handle encryption and decryption:

// Generate a random encryption key
async function generateEncryptionKey() {
  return window.crypto.subtle.generateKey(
    { name: "AES-GCM", length: 256 },
    true,
    ["encrypt", "decrypt"]
  );
}

// Encrypt data with the generated key
async function encryptData(key, data) {
  // Create a random initialization vector
  const iv = window.crypto.getRandomValues(new Uint8Array(12));
  
  // Encode the data as UTF-8
  const encodedData = new TextEncoder().encode(data);
  
  // Encrypt the data
  const encryptedBuffer = await window.crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    key,
    encodedData
  );
  
  // Convert the encrypted data and IV to base64
  const encryptedData = bufferToBase64(encryptedBuffer);
  const ivBase64 = bufferToBase64(iv);
  
  return { encryptedData, iv: ivBase64 };
}

// Decrypt data with the provided key
async function decryptData(key, encryptedData, iv) {
  // Convert base64 back to ArrayBuffer
  const encryptedBuffer = base64ToBuffer(encryptedData);
  const ivBuffer = base64ToBuffer(iv);
  
  // Decrypt the data
  const decryptedBuffer = await window.crypto.subtle.decrypt(
    { name: "AES-GCM", iv: ivBuffer },
    key,
    encryptedBuffer
  );
  
  // Decode the decrypted data as UTF-8
  return new TextDecoder().decode(decryptedBuffer);
}

Secret URL Generation

When a user creates a secret, we:

  1. Generate a random encryption key
  2. Encrypt the secret with this key
  3. Send the encrypted data to the server
  4. Receive a unique ID from the server
  5. Create a URL with the ID and the encryption key as a fragment

The encryption key is never sent to the server. Instead, it's added as a URL fragment (the part after the # symbol), which stays in the browser and isn't sent in HTTP requests.

// Create a secret URL
async function createSecretUrl(secret) {
  // Generate a random encryption key
  const key = await generateEncryptionKey();
  
  // Export the key to raw format
  const rawKey = await window.crypto.subtle.exportKey("raw", key);
  
  // Convert the key to base64
  const keyBase64 = bufferToBase64(rawKey);
  
  // Encrypt the secret
  const { encryptedData, iv } = await encryptData(key, secret);
  
  // Send the encrypted data to the server
  const response = await fetch('/api/store', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ encryptedData, iv })
  });
  
  const { id } = await response.json();
  
  // Create the secret URL with the key as a fragment
  return `${window.location.origin}/s/${id}#k=${keyBase64}`;
}

Privacy Benefits of Our Approach

Complete Privacy

Our zero-knowledge architecture ensures that even we cannot access your personal data.

Quick Implementation

The Cloudflare stack allowed us to deploy a privacy-focused solution in just 5 days.

No Data Retention

Secrets automatically vanish after being viewed, leaving no digital trail.

Deployment Process

Deploying our solution to production was straightforward thanks to Cloudflare's developer-friendly tools:

1. Setting Up the KV Namespace

# Create a KV namespace for secrets
wrangler kv:namespace create "SECRETS_STORE"

# Add the namespace to wrangler.toml
# kv_namespaces = [
#   { binding = "SECRETS_STORE", id = "your-namespace-id" }
# ]

2. Configuring CORS for Multiple Domains

Since we operate on two domains (vanishingvault.com and secretdropbox.com), we needed to configure CORS properly:

// CORS headers function
function corsHeaders(request) {
  // Get the origin from the request
  const origin = request.headers.get('Origin');
  const allowedOrigins = [
    'https://secretdropbox.com',
    'https://vanishingvault.com',
    'http://localhost:3000' // For development
  ];
  
  // Check if the origin is allowed
  const allowedOrigin = allowedOrigins.includes(origin) 
    ? origin 
    : allowedOrigins[0];
  
  return {
    'Access-Control-Allow-Origin': allowedOrigin,
    'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Max-Age': '86400',
  };
}

3. Deploying the Worker

# Deploy the worker
wrangler deploy

# Configure custom domain routes
# In your Cloudflare dashboard:
# - Add a route pattern: secretdropbox.com/api/*
# - Add a route pattern: vanishingvault.com/api/*

Real-World Use Cases

Our zero-knowledge secret sharing tool is designed to address a variety of secure sharing needs:

  • Share sensitive personal information with family members
  • Securely transmit passwords to friends
  • Send private account details to trusted contacts
  • Share temporary access codes without leaving a trace

Conclusion

VanishingVault gives privacy-conscious individuals a secure way to share sensitive information. By leveraging Cloudflare's powerful infrastructure and implementing zero-knowledge encryption, we've created a solution that prioritizes your privacy while remaining simple to use.

Ready to protect your personal information? Try VanishingVault today.

Get Started