Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Solo iOS Growth Diary — Vol. 5: AES-GCM in 200 ...

Solo iOS Growth Diary — Vol. 5: AES-GCM in 200 Lines (E2E Encryption for Solo iOS Devs)

For a single-user iOS memo app, CryptoKit alone is enough to ship real end-to-end encryption — no libsodium, no key server, no rolling your own. Vol. 5 of Solo iOS Growth Diary builds AES-GCM E2E in ~187 lines of Swift.

Inside: what an AEAD actually gives you (confidentiality + integrity + authenticity in one call), the SealedBox envelope (96-bit nonce ‖ ciphertext ‖ 128-bit tag), a Secure-Enclave-wrapped 256-bit SymmetricKey held in the Keychain (this-device-only, non-syncable), and the seal()/open() paths where .open() verifies the 128-bit tag before returning a single plaintext byte. Plus the one rule that matters — never reuse a (key, nonce) pair — charted against the NIST SP 800-38D 2^32 random-nonce rekey bound and real usage (~2.4M memos, zero collisions).

Anti-thesis for every claim: "Roll-your-own crypto = bad" (calling a vetted AEAD is the opposite of implementing your own cipher), "You need a big crypto library" (CryptoKit covers key-gen, AES-GCM and ChaChaPoly, HKDF, and Secure Enclave wrapping; the one gap — a password KDF — is a single CommonCrypto PBKDF2 call), and "Real E2E needs key exchange" (a memo-to-self vault is single-party — symmetric, no PKI, no server). The deck also marks the encryption boundary precisely: the message key protects the email/sync path, while an optional local-only append to an Obsidian daily note on iCloud Drive stays on-device under iOS Data Protection (iPhone-only; the vault's security is the platform's, not the app's message key).

Part of the 12-week Solo iOS Growth Diary — new deck every Tuesday 22:00 JST. Keywords: cryptokit aes-gcm, e2e encryption ios, ios keychain, secure enclave, aead swift, symmetrickey, authenticated encryption, nonce reuse, chachapoly, Captio.

Follow: speakerdeck.com/simplememo · x.com/simplememofast
References: simplememofast.com/obsidian/ · apps.apple.com/us/app/captio-style-simple-memo/id6758438948?ct=obsidian-en&mt=8

Avatar for SimpleMemo

SimpleMemo

June 09, 2026

More Decks by SimpleMemo

Other Decks in Programming

Transcript

  1. S O L O i O S G R O

    W T H D I A R Y VOL. 05 AES-GCM in 200 Lines E2E Encryption for Solo iOS Devs Why CryptoKit alone is enough — no libsodium, no key server, no rolling your own. CryptoKit AES-GCM 0 dependencies speakerdeck.com/simplememo · x.com/simplememofast · 2026-06-09
  2. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 02 / 35 The 12-week promise 01 Battle-tested patterns From a solo iOS founder shipping in public. 02 Real numbers, no mythology Cost, latency, bytes — measured and printed. 03 Anti-thesis every volume Three counter-arguments per topic. Survival > fashion. 04 Reproducible code Copy-paste over inspirational quotes.
  3. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 03 / 35 Today's roadmap 1 The plaintext that synced Slide 04–07 2 Why "roll your own" breaks Slide 08 3 Reframe: cipher vs key custody Slide 09–10 4 What an AEAD gives you Slide 11–12 5 Architecture & the actual code Slide 13–15 6 The nonce rule + key custody Slide 16–17 7 The encryption boundary Slide 18–19 8 Anti-thesis — three counter-punches Slide 20–22 9 Misuse, cost, weaknesses Slide 23–25 1 0 Playbook + Honest Scoreboard Slide 26–32
  4. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 04 / 35 Encrypting the bytes is the easy part. Holding the key is the design. So why do solo devs still believe E2E needs a crypto PhD and a key server?
  5. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 05 / 35 A short history of "don't roll your own" 2001 AES (Rijndael) standardized 2007 GCM → NIST SP 800-38D 2014 Heartbleed: OpenSSL bug 2016 Padding oracles still shipping 2019 CryptoKit ships (iOS 13) 2022 AEAD is the default advice 2026 E2E in ~187 lines of Swift Amber = the path to safe-by-default crypto. Every disaster came from composing primitives by hand; AEAD ended that.
  6. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 06 / 35 The plaintext that synced SimpleMemo v0.8 (pre-encryption era) Email-as-sync (Vol. 3) was the durable layer — but the memo body rode SMTP as plain text. It sat readable on disk, in transit, in the mailbox, and in every backup. The user never saw it; neither did we, until we threat- modeled it. 4 places a plaintext memo was exposed 0 of them were the user's choice 100% readable to anyone with the mailbox 1 fix: seal before it ever leaves RAM A memo is a diary entry, a password hint, a half-formed idea. "Synced" must not quietly mean "published to your mail provider." Insight → if the bytes can be read by anyone but the user, you didn't sync a memo — you leaked one.
  7. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 07 / 35 Threat model · what E2E must (and must not) cover End-to-end means the bytes are sealed on the device and only opened on a device the user controls. Map every place a memo rests or moves, then decide what the message key protects. At rest on disk Sealed AES-GCM ciphertext only; plaintext never written In transit (SMTP) Sealed Envelope crosses the network edge, not the body In the mailbox / on the server Sealed Provider stores ciphertext it cannot read In device & iCloud backups Sealed Backups capture ciphertext, not plaintext The decryption key On-device Keychain, this-device-only — never transmitted Subject line & timestamps Metadata Not secret by design — keep bodies in the body
  8. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 08 / 35 Why hand-rolled crypto breaks ECB mode "because it's simple" Identical blocks encrypt identically — the infamous penguin. Patterns leak straight through. Encrypt without authenticate AES-CBC alone has no integrity. An attacker flips bits; you decrypt attacker-chosen garbage. MAC-then-encrypt ordering Compose the MAC and cipher in the wrong order and you reopen padding-oracle attacks. Nonce / IV reuse Reusing a (key, nonce) pair in a stream/counter mode leaks plaintext XOR — and in GCM, the MAC key. Timing & padding oracles Non-constant-time compares and padding errors become bit-by-bit plaintext recovery. An AEAD deletes this whole column. "Don't roll your own" means don't build these — not don't encrypt.
  9. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 09 / 35 “ Use the AEAD. Guard the key. — Solo iOS Growth Diary · Vol. 5
  10. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 10 / 35 Reframe: the cipher is a call, custody is the system You don't "implement encryption." You make one library call and design a key's whole life. AES.GCM.seal() is two lines. The real work is: where the key is born, where it sleeps, when it's reachable, how it's wrapped, when it rotates, and what happens if it's lost. 256-bit symmetric data key (SymmetricKey) 1 call AES.GCM.seal / .open — the whole cipher 0 servers single-user E2E needs no key exchange Keychain the key's home — this device only
  11. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 11 / 35 What an AEAD gives you in one call AES-GCM is Authenticated Encryption with Associated Data: confidentiality, integrity, and authenticity from a single primitive. plaintext the memo bytes key 256-bit, Keychain nonce 96-bit, fresh AAD memo-id (bound, not encrypted) AES.GCM.seal() hardware AES, constant-time ciphertext same length as plaintext tag (128-bit) GMAC — detects any tamper ▶ ▶ One primitive, three guarantees: nobody can read it (confidentiality), nobody can change it undetected (integrity), and it provably came from the holder of the key (authenticity).
  12. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 12 / 35 The envelope · what actually gets stored AES.GCM.SealedBox.combined concatenates everything the decrypt side needs into one Data blob. That blob — and only that blob — hits disk, the Outbox (Vol. 4), and SMTP (Vol. 3). nonce 12 B ciphertext n B (= plaintext length) tag 16 B ← one Data blob: SealedBox.combined → W H Y I T ' S S E L F - C O N T A I N E D The nonce is not a secret — it ships in the clear alongside the ciphertext. It only must be unique per key. The tag binds the whole thing: change one byte of nonce, ciphertext, or AAD and .open() throws before returning a single plaintext byte.
  13. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 13 / 35 Architecture · the key never leaves the device Secure Enclave P-256 key non-exportable, in hardware wraps ▶ Data key (Keychain) 256-bit AES key, wrapped this-device-only wraps ▶ Each memo AES-GCM, fresh nonce, memo-id as AAD …then only ciphertext flows downstream: Ciphertext nonce ‖ ct ‖ tag ▶ Outbox (WAL) durable, append-only · Vol. 4 ▶ SMTP / email sync layer · Vol. 3 The plaintext exists only in RAM, only while the app is unlocked. Disk, queue, network, and backups see ciphertext — and the key that opens it is pinned to this one device.
  14. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 14 / 35 Code · seal() — encrypt before anything else import CryptoKit // 'key' is a 256-bit SymmetricKey, created once and kept in the // Keychain (this device only). It never leaves the device. func seal(_ memo: Data, key: SymmetricKey, memoID: UUID) throws -> Data { let nonce = AES.GCM.Nonce() // fresh 96-bit random nonce let aad = withUnsafeBytes(of: memoID.uuid) { Data($0) } let box = try AES.GCM.seal(memo, // confidentiality using: key, nonce: nonce, authenticating: aad) // integrity + binding // combined = nonce(12) ‖ ciphertext ‖ tag(16) guard let envelope = box.combined else { throw CryptoError.seal } return envelope // the ONLY thing we ever write or send } Two CryptoKit calls. The discipline is in the order: seal first, then persist or render — never the reverse.
  15. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 15 / 35 Code · open() — verify, then decrypt func open(_ envelope: Data, key: SymmetricKey, memoID: UUID) throws -> Data { let box = try AES.GCM.SealedBox(combined: envelope) let aad = withUnsafeBytes(of: memoID.uuid) { Data($0) } // .open() checks the 128-bit tag over (ciphertext + AAD) FIRST. // Wrong key, flipped bit, or swapped memo-id -> it throws. // It never returns partial or unauthenticated plaintext. return try AES.GCM.open(box, using: key, authenticating: aad) } // A tampered or truncated envelope surfaces as a thrown error, // not as corrupted text on screen: do { let memo = try open(blob, key: k, memoID: id) } catch { showCorruptedBadge() } // fail closed, never fail open Authenticate-then-decrypt is the default here — you can't accidentally read unverified bytes.
  16. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 16 / 35 The one rule · never reuse a (key, nonce) pair GCM's only catastrophic footgun. A random 96-bit nonce makes collisions a birthday problem: p ≈ n² / 2⁹⁷. NIST SP 800-38D: rekey after 2³² messages to keep p < 2⁻³². 10⁰ 10² 10⁴ 10⁶ 10⁸ 10¹⁰ negligible-collision zone 2³² · NIST rekey line (random nonce) SimpleMemo: every memo ever encrypted ≈ 2.4 M ~1,800× below the rekey line, lifetime 0 nonce collisions in production AES.GCM.Nonce() fresh random per message
  17. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 17 / 35 Key custody · the load-bearing decision K E Y C H A I N S T O R E // Store ONLY a wrapped 256-bit data key, pinned to this device. let raw = dataKey.withUnsafeBytes { Data($0) } // 32 bytes let q: [String: Any] = [ kSecClass as String: kSecClassKey, kSecAttrApplicationTag as String: tag, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, // ← gate kSecAttrSynchronizable as String: false, // ← no iCloud kSecValueData as String: raw, ] SecItemAdd(q as CFDictionary, nil) …ThisDeviceOnly key can't ride a backup to another device synchronizable = false key never enters iCloud Keychain Secure-Enclave wrap data key sealed by a hardware P-256 key unlock-gated unreadable while the phone is locked E2E lives or dies here: the cipher is public and strong, so the only thing standing between a memo and the world is where this 32-byte key sleeps. Keep it on one device, behind the lock screen, never synced.
  18. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 18 / 35 The encryption boundary · where E2E starts and stops A useful way to teach "end-to-end" is to point at the exact line the message key protects — and what sits on either side of it. I N S I D E · t h e m e s s a g e k e y The email / sync path is AES-GCM sealed. Only ciphertext crosses the network edge — disk, Outbox, SMTP, mailbox, backups all see the envelope, never the plaintext. plaintext → seal → ciphertext → network End-to-end, single-party: only this device's key reopens it. OUTSIDE · the platform's protection The optional local-only append to an Obsidian daily note (yyyy-MM- dd.md) on iCloud Drive is a device-side file write. It deliberately sits outside the network-message boundary — protected by iOS Data Protection + iCloud's own encryption, not the app's AES-GCM email key. local append → vault file (on device) F A I R P R I N T Advanced-Mode, opt-in. iPhone-only today; the vault must live on iCloud Drive or "on this iPhone"; the file is protected by NSFileProtection and iCloud's encryption — treat the vault's security as the platform's, not the app's message key. The write is instant; iCloud reflection can lag a few seconds. Ref: simplememofast.com/obsidian/
  19. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 19 / 35 AES-GCM vs ChaChaPoly vs roll-your-own vs libsodium Property CryptoKit AES-GCM CryptoKit ChaChaPoly AES-CBC + HMAC (DIY) libsodium AEAD (auth built-in) Yes Yes Only if composed right Yes In the OS already Yes Yes n/a No — vendored Hardware-accelerated Yes (AES) SW (fast) Varies Yes Misuse surface Nonce reuse Nonce reuse Many footguns Low Added dependency 0 0 0 (but you own bugs) 1 + CVE surface Right for Default choice No-AES-HW targets Almost never Cross-platform/ extras AES-GCM is the default; ChaChaPoly is the same deal without AES hardware. Both ship free in CryptoKit. DIY and libsodium are the two ways to add risk or weight you don't need here.
  20. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 20 / 35 Anti-thesis 1 · "Roll-your-own crypto = bad." They say: “Never implement your own cryptography. Amateurs ship broken ciphers. Stay away from encryption unless you have a cryptographer on the team.” Reality: The rule warns against implementing the cipher/mode yourself Calling AES.GCM.seal is consuming Apple's vetted AEAD AEAD removes the footguns the rule is about It's hardware-backed, constant-time, and audited Not encrypting is the actually-dangerous choice
  21. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 21 / 35 Anti-thesis 2 · "You need a big crypto library." “Use libsodium / Tink / RNCryptor. A real app needs a battle-hardened crypto library, not the system framework.” F o r a s i n g l e - u s e r m e m o a p p , C r y p t o K i t a l r e a d y c o v e r s i t : Key generation SymmetricKey(size: .bits256) — CSPRNG-backed AEAD AES.GCM and ChaChaPoly, both in-framework Key derivation HKDF<SHA256> for per-purpose subkeys Hardware key wrap SecureEnclave.P256 — non-exportable The one real gap a password KDF → one CommonCrypto PBKDF2 call
  22. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 22 / 35 Anti-thesis 3 · "Real E2E needs key exchange." A S Y M M E T R I C E V E R Y T H I N G “You need RSA/ECDH key exchange, a PKI, and a key server before you can call anything end- to-end.” • Asymmetric crypto exists to share a secret with a 2nd party • It adds PKI, trust, revocation, and a server to run • That's the multi-party problem • A memo-to-self vault has exactly one party S I N G L E - P A R T Y E 2 E “The user is sender and recipient. Symmetric is the whole story.” • One 256-bit key, on one device • No exchange — nobody to send it to • No server, no PKI, no revocation • The key lives in the Keychain and never transmits • Reach for P-256 / HPKE only when you add real sharing (a later Vol.)
  23. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 23 / 35 Defanging the one footgun · nonce-misuse resistance If you can't guarantee unique nonces (counter reset on restore, multi-process writers, cloned VMs), don't pray — pick a primitive that forgives reuse, or make reuse structurally impossible. Random + rekey budget AES.GCM.Nonce() per message; rotate the key well before 2³². Simplest; what SimpleMemo ships. ChaChaPoly Same AEAD contract, in CryptoKit. Use where AES hardware is absent; still nonce- sensitive. SIV mode AES-GCM-SIV survives nonce reuse — but it's via swift-crypto, NOT in stock CryptoKit. Add only if you must. Rule of thumb: random nonce + a rekey budget covers a single-user app. Reach for SIV only when uniqueness is genuinely out of your control.
  24. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 24 / 35 Cost story · CryptoKit vs the alternatives CryptoKit AES-GCM 0 third-party deps ~187 lines · 0 CVEs to patch libsodium / Tink 1 vendored stack binary size + its own CVE feed Roll-your-own ∞ footguns to audit padding oracles · pro audit $$$ CryptoKit buys audited, hardware-backed AEAD at zero dependency cost. The library you don't import is a CVE feed you don't track and a binary you don't bloat.
  25. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 25 / 35 Where CryptoKit / AES-GCM is the wrong call Sharing a memo with another person That's multi-party. Now you do need P-256 / HPKE key exchange — symmetric-only won't cut it. Password-derived keys CryptoKit has no PBKDF2 / Argon2. Use CommonCrypto's CCKeyDerivationPBKDF or swift-argon2. Forward secrecy at rest One long-lived key has none. Rotate keys and re-encrypt if a compromise window matters. Very large data under one key GCM caps near ~64 GB per key and 2³² random-nonce messages. Rekey before you get there. Key recovery True E2E has no server-side recovery. Lost key = lost data unless the user holds recovery material. A single-user memo vault hits none of these on day one. Know the edges before you cross them.
  26. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 26 / 35 Playbook · Step 1 of 7 Use an AEAD, not a bare cipher AES.GCM.seal/.open — never raw AES-CBC, never encrypt-without-authenticate. H O W let box = try AES.GCM.seal(memo, using: key, nonce: AES.GCM.Nonce(), authenticating: aad) W H Y Confidentiality + integrity + authenticity from one primitive. The footguns live in the modes you're not writing.
  27. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 27 / 35 Playbook · Step 2 of 7 One fresh random nonce per message AES.GCM.Nonce() each time; never reuse a (key, nonce) pair; budget a rekey before 2³². H O W let nonce = AES.GCM.Nonce() // 96-bit, CSPRNG // p(collision) ≈ n² / 2⁹⁷ → rekey well before 2³² msgs W H Y This is GCM's single catastrophic rule. Make it impossible to break, not merely unlikely.
  28. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 28 / 35 Playbook · Step 3 of 7 Keep the key in the Keychain, this-device-only …WhenUnlockedThisDeviceOnly, synchronizable = false, wrapped by a Secure-Enclave key. H O W kSecAttrAccessible: …WhenUnlockedThisDeviceOnly kSecAttrSynchronizable: false // never iCloud Keychain W H Y The cipher is public; the key is the secret. Its storage attributes ARE your security model.
  29. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 29 / 35 Playbook · Step 4 of 7 Seal at the boundary Encrypt before the memo touches disk, the Outbox, or the network. Plaintext never persists. H O W let blob = try seal(memo, key: k, memoID: id) outbox.enqueue(blob) // ciphertext only, from here on W H Y Plaintext should exist only in RAM, only while unlocked. Everything durable is an envelope.
  30. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 30 / 35 Playbook · Steps 5–7 (bind, version, recover) 5 Authenticate context with AAD Bind ciphertext to its memo-id / version so a row can't be silently swapped for another. AAD is free integrity for metadata you don't encrypt. Bind what must not be swapped. 6 Version the envelope A v byte + key-id lets you migrate ciphers or rotate keys without a flag day. Crypto you ship is crypto you'll migrate. Leave yourself a seam before you need it. 7 Plan for key loss honestly E2E means you can't recover it either. Offer a user-held recovery key — or say plainly that a lost key = lost data. Silent escrow breaks the E2E promise. Honesty about recovery is part of the security model.
  31. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 31 / 35 Honest scoreboard · the crypto subsystem 100% memos encrypted at rest (256-bit AES-GCM) 0 plaintext bytes that leave the device 0 third-party crypto deps (CryptoKit is in iOS) 0.18 ms median seal of a 1 KB memo (hardware AES) 1.9 GB/s AES-GCM throughput (A17, hardware AES) 187 lines of Swift in the crypto subsystem Nonce: 96-bit random · Tag: 128-bit · Key: 256-bit. Source: SimpleMemo crypto telemetry, 2026-Q2. Re-printed in Vol. 12's year-one retrospective.
  32. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 32 / 35 Where it broke (so you don't have to find out) 2025-07-19 Counter-based nonce reuse risk on restore A nonce counter in UserDefaults reset on backup-restore → (key, nonce) reuse risk. Switched to random AES.GCM.Nonce(). 2025-10-02 Key was syncable key rode iCloud Keychain item was …WhenUnlocked + synchronizable. The data key reached a 2nd device. Tightened to ThisDeviceOnly + non-syncable. 2026-02-11 No AAD binding swap-able ciphertext A corrupt mailbox could swap ciphertext between memo-ids. Added memo-id as AAD; .open now rejects mismatches. 2026-04-03 SHA-256(password) as key no real KDF A passphrase feature hashed the password directly. Replaced with PBKDF2 (CommonCrypto, 600k iters). Pattern: every bug was in key handling or the nonce — never in AES-GCM itself. Guard the inputs; trust the primitive.
  33. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 33 / 35 What to copy, what to skip C O P Y • AES.GCM.seal / .open as your whole cipher (an AEAD) • A fresh AES.GCM.Nonce() on every single message • 256-bit key in the Keychain, this-device-only, non-syncable • Seal before disk / queue / network — plaintext stays in RAM S K I P • Hand-rolled AES-CBC, ECB, or your own MAC composition • Reusing or counter-deriving a nonce without a rekey budget • A second crypto library you then have to keep patched • Asymmetric / a key server for a single-user vault
  34. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 34 / 35 Strong encryption is a solved, two-line problem. Spend your worry on the key, not the cipher. — @simplememofast
  35. Solo iOS Growth Diary · Vol. 5 · AES-GCM in

    200 Lines 35 / 35 Next week · Vol. 6 V O L . 0 6 ASO with No Budget — Keyword Landscape for Memo Apps in 2026 Thesis: exploit the ghosts of dead apps. How a solo dev maps a memo-app keyword landscape — Captio, EOL heirs, live clusters — and ranks with zero paid UA. F O L L O W speakerdeck.com/simplememo x.com/simplememofast New deck every Tuesday 22:00 JST. P U B L I S H E D Vol. 1 · From EOL to Encore Vol. 2 · Speed Is The Feature Vol. 3 · Email-as-Sync Vol. 4 · The Outbox Vol. 5 · AES-GCM in 200 Lines ← you are here