diff4

Encryption

End-to-end encryption reference for the Diff and Files APIs.

Overview

All payloads sent to the Diff and Files APIs are encrypted client-side using AES-256-GCM with a PBKDF2-derived key. The server only stores the encrypted blob and cannot read the content. Viewers must enter the correct passphrase to decrypt.

Parameters

ParameterValue
AlgorithmAES-256-GCM
Key DerivationPBKDF2-SHA256, 600,000 iterations
SaltRandom 16 bytes (base64)
IVRandom 12 bytes (base64)

Plaintext Format

Diff API

The plaintext to encrypt is a JSON object containing title and files:

{
  "title": "Fix login bug",
  "files": [
    {
      "filename": "src/auth.ts",
      "language": "typescript",
      "patch": "@@ -1,3 +1,4 @@\n..."
    }
  ]
}

Files API

The plaintext to encrypt is a JSON object containing a files array:

{
  "files": [
    {
      "title": "src/auth.ts",
      "content": "export function login(token: string) {\n  ...\n}"
    }
  ]
}

Encryption Example

const PBKDF2_ITERATIONS = 600_000;

async function deriveKey(passphrase: string, salt: Uint8Array): Promise<CryptoKey> {
  const keyMaterial = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(passphrase),
    "PBKDF2",
    false,
    ["deriveKey"],
  );
  return crypto.subtle.deriveKey(
    { name: "PBKDF2", salt, iterations: PBKDF2_ITERATIONS, hash: "SHA-256" },
    keyMaterial,
    { name: "AES-GCM", length: 256 },
    false,
    ["encrypt"],
  );
}

function toBase64(buffer: ArrayBuffer): string {
  return btoa(String.fromCharCode(...new Uint8Array(buffer)));
}

async function encrypt(plaintext: string, passphrase: string) {
  const salt = crypto.getRandomValues(new Uint8Array(16));
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const key = await deriveKey(passphrase, salt);
  const ciphertext = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    key,
    new TextEncoder().encode(plaintext),
  );
  return {
    ciphertext: toBase64(ciphertext),
    iv: toBase64(iv.buffer as ArrayBuffer),
    salt: toBase64(salt.buffer as ArrayBuffer),
  };
}
import base64
import hashlib
import os

from cryptography.hazmat.primitives.ciphers.aead import AESGCM

PBKDF2_ITERATIONS = 600_000


def encrypt(plaintext: str, passphrase: str) -> dict:
    salt = os.urandom(16)
    iv = os.urandom(12)

    key = hashlib.pbkdf2_hmac("sha256", passphrase.encode(), salt, PBKDF2_ITERATIONS, dklen=32)
    aesgcm = AESGCM(key)
    ciphertext = aesgcm.encrypt(iv, plaintext.encode(), None)

    return {
        "ciphertext": base64.b64encode(ciphertext).decode(),
        "iv": base64.b64encode(iv).decode(),
        "salt": base64.b64encode(salt).decode(),
    }


# Usage:
# payload = json.dumps({"title": "...", "files": [...]})
# encrypted = encrypt(payload, "my-passphrase")
# Then POST { "encrypted_data": encrypted } to /api/diff or /api/files

Requires the cryptography package: pip install cryptography

On this page