KrypticMail — building E2EE for people who hate encryption
End-to-end encrypted email is a solved problem academically. Making it usable? That's the hard part. Here's what I learned building KrypticMail2.
I've been building KrypticMail2 for a while now. It's an encrypted messaging platform (email-style, not chat-style) with end-to-end encryption. The crypto part is easy. The UX part nearly broke me.
the crypto
The stack is standard:
- Key exchange: X25519
- Encryption: AES-256-GCM
- Signing: Ed25519
- Ratchet: Double ratchet for forward secrecy
Nothing novel here. These are battle-tested primitives. If you're rolling your own crypto, you're doing it wrong. I'm standing on the shoulders of libsodium and everyone who audited it.
the hard part
Key discovery. Signal and WhatsApp solve this because they're synchronous — both parties are online, keys get exchanged during the handshake. Email is async. Alice might send a message to Bob while Bob hasn't even created his account yet. Where does Alice find Bob's public key?
We tried:
- Key server — Alice publishes her key, Bob fetches it. Problem: the key server is a trust point. If it's compromised, all keys are compromised.
- Key included in first message — Classic PGP style. But then your first message isn't encrypted until the recipient has your key.
- DNS-based keys — Like OpenPGP certificates in DNS. Elegant but DNS is slow and cache invalidation is painful.
We went with option 2 with a trust-on-first-use (TOFU) twist. The first message establishes keys. Subsequent messages use the established keys. If the key changes, we detect it and warn the user. Not perfect, but usable.
Metadata leakage. Even if the message content is encrypted, the subject line, sender, recipient, and timestamp are visible. Email protocols leak metadata by design. We had to implement a separate metadata channel that's also encrypted, which... adds complexity.
Forward secrecy. If someone's private key is compromised six months from now, all their past messages should still be safe. Double ratchet handles this, but implementing it correctly with the async email model (vs. synchronous chat) required some modifications to the ratchet step timing.
the UX
Encryption UX is stuck in 1995. "Import your PGP key" is not a thing normal people do. KrypticMail2 uses device-based key generation with biometric backup:
- Install app → keys generated on device
- Biometric auth → keys accessible
- Send message → encrypted automatically
- Receive message → decrypted automatically
The user never sees a key. They never manage a certificate. The encryption just works — or at least that's the goal.
current status
Operational, used internally. The codebase is private but I'm considering extracting the crypto primitives into an open-source library. If you're building anything with E2EE, the hardest part isn't the crypto — it's figuring out how to handle key distribution and metadata. Don't start from scratch.