Skip to content

URI Scheme for End-to-End Encrypted File Links #56

@brospars

Description

@brospars

Introduction

This proposal defines a new URI scheme, e2e:, to standardize the sharing of end-to-end encrypted files via links. The scheme enables users to share encrypted files without requiring custom client-side logic or out-of-band key exchange, leveraging existing web standards like the Web Crypto API.

Motivation:

  • Users need a simple, standardized way to share encrypted files without exposing keys to servers hosting the files.
  • No existing URI scheme supports encrypted file links natively.
  • The scheme should work with current web technologies (e.g., Web Crypto, fetch).

URI Scheme

Syntax

The e2e: URI scheme is structured as:

e2e:<key>:<iv>:<url>
  • <key>: URL-safe base64-encoded encryption key (e.g., AES-GCM).
  • <iv>: URL-safe base64-encoded initialization vector (IV).
  • <url>: URL of the encrypted file (e.g., https://example.com/file.enc).

Example:

e2e:SGVsbG8gV29ybGQh:SGVsbG8gV29ybGQh:https://example.com/files/123.enc

Workflow

  • Encryption:

    • Users encrypt files client-side using the Web Crypto API (e.g., AES-GCM).
    • The key and IV are encoded as URL-safe base64 and embedded in the e2e: link.
  • Sharing:

    • The e2e: link is shared via email, messaging, or other channels.
  • Decryption:

    • Recipients browser parses the e2e: link to extract the key, IV, and file URL, downloads the encrypted file and decrypts it using the Web Crypto API and then uses it as a normal file.

Use Cases

Alice Shares a File with Bob

  1. Alice encrypts file.txt with a random key/IV using Web Crypto.
  2. Alice constructs an e2e: link:
    e2e:<key>:<iv>:https://example.com/files/123.enc
    
  3. Alice shares the link with Bob (e.g., via email).
  4. Bob’s browser/web app:
    • Parses the e2e: link.
    • Downloads https://example.com/files/123.enc.
    • Decrypts the file using the embedded key/IV.
    • Uses the file as a normal File Blob

Browser Integration (Optional)

  • Browsers could natively support e2e: links by:
    • Detecting the scheme and prompting the user to decrypt the file.
    • Falling back to external apps if no native support exists.

Security Considerations

  • Servers only store encrypted files and cannot decrypt them without the key.
  • Keys are embedded in the URI and must be shared securely (e.g., via encrypted messaging).
  • The URI itself may leak metadata (e.g., file location). Users should avoid sharing e2e: links publicly.
  • AES-GCM (or another authenticated encryption algorithm) is recommended.
  • IV must be unique per file and included in the URI.
  • 256-bit keys for AES-GCM.

Backward Compatibility

  • Browsers without e2e: support will treat it as an unrecognized scheme (e.g., prompt to open an external app).
  • Web apps can polyfill support by parsing e2e: links manually.

Example Implementation

Constructing an e2e: Link (JavaScript)

// Encrypt a file
const key = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt"]);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, fileData);

// Encode key/IV for URI
const keyBase64 = btoa(String.fromCharCode(...new Uint8Array(await crypto.subtle.exportKey("raw", key))))
  .replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
const ivBase64 = btoa(String.fromCharCode(...iv))
  .replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");

// Construct e2e: link
const e2eLink = `e2e:${keyBase64}:${ivBase64}:${fileUrl}`;

Decrypting an e2e: Link

// Parse e2e: link
const [_, keyB64, ivB64, url] = e2eLink.match(/^e2e:([^:]+):([^:]+):(.+)$/);

// Decode key/IV
const keyBuffer = Uint8Array.from(atob(keyB64.replace(/-/g, "+").replace(/_/g, "/")), c => c.charCodeAt(0));
const ivBuffer = Uint8Array.from(atob(ivB64.replace(/-/g, "+").replace(/_/g, "/")), c => c.charCodeAt(0));
const key = await crypto.subtle.importKey("raw", keyBuffer, { name: "AES-GCM" }, false, ["decrypt"]);

// Download and decrypt
const response = await fetch(url);
const encryptedData = new Uint8Array(await response.arrayBuffer());
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv: ivBuffer }, key, encryptedData);

Open Questions

  • Key/IV Encoding: Is base64url the best choice, or should we use another format?
  • Algorithm Agility: How to support multiple algorithms (e.g., ChaCha20) in the URI?

Looking forward to your thoughts!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions