How a dove transfer moves.
Follow plaintext, ciphertext, keys, metadata, and access policy through outgoing shares and browser-based file requests.
Two tiers, two security boundaries
| Simple | Full | |
|---|---|---|
| Stored object | Plaintext unless --encrypt is set | Always client-side encrypted |
| Link | S3 presigned GET, at most 7 days | Gate URL plus a fragment secret |
| Policy | Expiry only | Expiry, one/N downloads, optional PIN |
| Server can read file? | Yes, unless optional encryption is used | No—the content key never reaches it |
| Recipient | Direct download; encrypted links use dove get | Browser under 2 GB; CLI for larger files |
The full-mode data path
- Prepare locally. A directory is zipped. A random 32-byte secret is generated. If the share has a PIN, the content key is derived with PBKDF2-HMAC-SHA256 from the PIN and fragment secret.
- Encrypt locally. dove writes a versioned, chunked AES-256-GCM container to a temporary file before upload.
- Upload ciphertext. S3 receives a name-free object key. The plaintext filename, sender name, and message are encrypted separately with a domain-separated metadata key.
- Register policy. The scoped signing identity writes the expiry, download budget, ciphertext size, encrypted metadata, and optional PIN verifier to DynamoDB.
- Print the link. The gate URL carries a MAC-authenticated share ID. The high-entropy secret lives after
#. - Preview safely. Opening
/d/<id>serves static HTML. Its free/meta/<id>read does not decrement the budget. - Act explicitly. Only a recipient click reaches
/dl/<id>. The gate atomically decrements policy and redirects to a 15-minute presigned S3 GET. - Decrypt locally. The browser or CLI authenticates every chunk and writes the plaintext on the recipient’s device.
The file-request data path
- Create locally.
dove requestgenerates a fragment secret, encrypts the requester name, message, and description, registers expiry/PIN/upload policy, and records the fragment in the requester’s local ledger. - Preview safely. Opening
/r/<id>serves static HTML./rmeta/<id>returns status and opaque encrypted request metadata without authorizing an upload. - Verify access. If configured, the uploader submits the PIN to the rate-limited gate. The PIN authorizes upload but is not the request’s content key.
- Choose explicitly. The page does not read or upload a file until the person chooses one and clicks Encrypt & send file.
- Encrypt locally. The browser buffers one file, encrypts it in the same authenticated chunked container using the fragment secret, and keeps plaintext off the network.
- Upload ciphertext.
/up/<id>returns a short-lived presigned S3 POST with a gate-chosen object key and a 512 MiB content-length ceiling./done/<id>confirms the object and marks the request received. - Collect locally.
dove requests get <id>uses the fragment retained in the local request ledger to download, authenticate, decrypt, and save the file.
Why the fragment matters
https://share.example.com/d/4c8f…d02a#M9g…fragment-secret The browser sends the origin and path in HTTP. It does not send the fragment. JavaScript on the loaded page can read location.hash locally, which lets the recipient UI derive a key that CloudFront, API Gateway, Lambda, DynamoDB, S3, DNS, and ordinary access logs never receive.
This is a structural claim, not a policy promise. The serving infrastructure lacks the key material needed to decrypt a full-mode file.
Encrypted container format
The current implementation uses a 1 MiB default plaintext chunk size. Integers are big-endian.
header: "DOVE" | version:u8 | nonce_prefix:[u8;8] | chunk_size:u32
chunk: is_last:u8 | ct_len:u32 | ciphertext_and_tag[ct_len] A nonce is the random 8-byte file prefix plus a 32-bit chunk counter. The counter and final-chunk flag are authenticated as additional data. Reordering, truncation, an altered terminal flag, a wrong key, or changed ciphertext fails authentication.
PINs split access from confidentiality
The gate checks a PIN online and locks the share after five wrong attempts. Separately, the client derives the content key with 600,000 PBKDF2-HMAC-SHA256 iterations using the fragment secret as salt. The gate sees a submitted PIN, but never the fragment secret. The link carries the fragment secret, but not the PIN.
The pre-check endpoint is /verify/<id>?pin=…; it rate-limits without spending a download. The explicit download still supplies the PIN to /dl/<id>. Do not put PINs in application logs.
Browser versus CLI
The default recipient page checks metadata first. Outgoing shares smaller than 2 GB use WebCrypto in the browser and save locally. Larger shares show their size and a complete dove get <url> handoff. Request uploaders always use the browser in the current release: one file, up to 512 MiB, is buffered and encrypted locally before upload. These are product thresholds, not cryptographic ones.