Use case
End-to-end encrypted
Encrypt in the browser with a passphrase. Your server and your bucket store bytes they cannot read.
const key = await deriveKey(passphrase, salt) // PBKDF2 or Argon2, in the browser
const iv = crypto.getRandomValues(new Uint8Array(12))
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded)
// The transfer holds the salt, the IV and the ciphertext. Nothing else.
await transfers.createSnapshot({
data: { salt: toBase64(salt), iv: toBase64(iv), ciphertext: toBase64(ciphertext) },
version: 3,
})The situation
What is actually going on.
The problem
By default your server can read every transfer it stores. For a journal, a password manager, health data or a contract, that is the wrong default, and a liability you may not want to hold.
What s3nd does about it
s3nd stores whatever you hand it. Derive a key from a passphrase with WebCrypto, encrypt the payload in the browser, and send the ciphertext. The bucket, your server and anyone who guesses the code see bytes they cannot use; the passphrase travels with the person, not over the wire. The same works for a file as for a snapshot.
Worth watching
The things that are easy to get wrong.
Compression happens after encryption
Ciphertext does not compress. Expect the stored size to match the raw size, and set maxSize accordingly.
Two secrets, two channels
The code goes over one channel and the passphrase over another, or in the person’s head. A code alone is now worth nothing to a guesser.
A lost passphrase is lost data
That is what end-to-end means. Say so in the interface before the user relies on it.
Related
Other shapes of the same primitive.
Put a file. Hand over the code.
Point it at the bucket you already pay for. Nothing to deploy, nothing to sign up for, nothing in the middle.
npm install -g @s3nd/clis3nd init --provider r2 --bucket drops3nd put ./anything.zip