Encrypted Transfer
vault
Encrypted document exchange. Files are encrypted in your browser before they leave your device. The server stores ciphertext. The key exists only in the link you share.
AES-256-GCMHKDF-SHA256Web Crypto APICloudflare WorkersR2 StorageTypeScriptHonoZero Dependencies (crypto)
01 / Engineering
How it works
Client-side encryption, zero-knowledge server, research-first architecture.
02 / The Code
From the source
AES-GCM encryption, HKDF key derivation, and one-time download deletion. Copied from the repo.
Client-Side Encryption
AES-256-GCM via Web Crypto API. Key derived via HKDF. Nonce prepended to ciphertext.
export async function encrypt(
plaintext: ArrayBuffer,
rawKey: Uint8Array,
): Promise<ArrayBuffer> {
const key = await deriveEncryptionKey(rawKey);
const nonce = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: nonce },
key,
plaintext,
);
// Prepend nonce to ciphertext
const result = new Uint8Array(12 + ciphertext.byteLength);
result.set(nonce, 0);
result.set(new Uint8Array(ciphertext), 12);
return result.buffer;
}HKDF Key Expansion
128-bit random key → 256-bit AES-GCM key. Non-extractable CryptoKey stays in browser engine.
export async function deriveEncryptionKey(
rawKey: Uint8Array,
): Promise<CryptoKey> {
const keyMaterial = await crypto.subtle.importKey(
"raw", rawKey, "HKDF", false, ["deriveKey"],
);
return crypto.subtle.deriveKey(
{
name: "HKDF",
hash: "SHA-256",
salt: new TextEncoder().encode("vault-enc"),
info: new TextEncoder().encode("aes-256-gcm"),
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
false, // non-extractable
["encrypt", "decrypt"],
);
}One-Time Download
Increment count, delete if final download. Blob gone before the response finishes.
// Increment download count
const newCount = await incrementDownloads(c.env, id);
// If this was the last allowed download, delete immediately
if (newCount >= meta.max_downloads) {
await deleteBlob(c.env, id);
}
return new Response(blob, {
status: 200,
headers: {
"Content-Type": "application/octet-stream",
"Cache-Control": "no-store",
"X-Downloads-Remaining": String(
Math.max(0, meta.max_downloads - newCount)
),
},
});0
tests passing
0
research documents
0
external crypto deps
0
limitations stated