Personal project · 01
Canopy
A simple secrets manager
About 9 years back, when I was still pursuing my MBA, I had a friend who also happened to be my PG-mate. He was old school and carried a lot of cash (yes, he is a baniya).
One evening I saw him reading something from a piece of paper and typing it on his phone. I asked him what that was, and he told me it was his bank-account information. He kept all such information like that.
My simple question was: why not use an app? There were over a dozen available. The only reason I could think of was trust. And because I liked to code, I told him — let me write an application for you to use. And I did.
I called it Treepass, owing to the background image I used in the application. It was a native Android app written in Java, where one could store any text secrets such as account information, passwords and more. Not very intuitive, yet it worked. Security was the feature I was the most proud of. All secrets were always encrypted on disk, and only decrypted when the app was opened, and only ever held in memory. It did not require any internet to run, so you could be sure your secrets did not leave your device to sit on a server. I even had screen-record protection, where screenshots and screen recording were not allowed (at least on the device).
Technical features aside, there were a lot of limitations to the app, and I kept punting feature enhancements. I also moved to Flutter, leaving my tough journey with a Java-based application behind.
Now, with the AI revolution — where everyone says you can build apps in days with just prompts — I thought to myself: why not rebuild this app now? I was sure it was simple enough to be built within a week, max, and Claude Code looked very promising. Well, it was easier said than done.
I started a new Claude Code session, giving it access to the code I had written so it could understand what I had built and what I expected. What I figured would be done in a week took over a month and a half to build, on the $100 Claude Code subscription. Important to mention: there were times when I had to look into the code and give directions on fixing certain bugs that it could not get right, even after multiple tries. There were times it broke completely-working features midway through implementing some other feature. There were also times when it implemented big features in a single session — ones that would easily have taken me weeks to get right, had I written that code myself.
With all that said, here is what Claude gave me.
Canopy
Meet Canopy — a multi-platform, offline secrets manager, with the ability to run the same account on multiple devices with the help of peer-to-peer sync. Here are the for Android and Windows (Mac, iPhone and iPad don't allow distributable files unless I pay them $100/year — I'd do that if I'm ever rich in the future. I do run it on my own Apple devices using a developer certificate, though, and it works just as well as on the others).
Below are the details of the app, which I again asked Claude to write.
The one-sentence version
Canopy is a local-first, peer-to-peer, end-to-end-encrypted secrets manager: your vault lives on your own devices, syncs directly between them over your own network with no server in the middle, and is sealed with post-quantum hybrid cryptography so that a secret captured today cannot be decrypted by a quantum computer tomorrow.
There is no account to create, no cloud to trust, and no company that can be breached, subpoenaed, or shut down to take your data with it. The devices you own are the entire system.
Part 1 · What it does
Feature spec
Everything Canopy does for you, feature by feature. The how-it-works cryptography comes right after.
A vault that is yours, on hardware you hold
- Local-first. Every secret is stored and decrypted on-device. The app is fully functional with no network at all — offline is not a degraded mode, it is the normal mode.
- No accounts, no servers. There is nothing to sign up for and nowhere your data is uploaded. Canopy never sees your vault because there is no Canopy backend.
- Cross-platform. One codebase runs on macOS, Windows, iOS, iPadOS, and Android, with a shared vault format so any of your devices can hold the whole thing.
- Multi-modal — not just passwords. Store typed fields and notes, but also full media files: images, documents, recordings and attachments live right inside the vault, each sealed the same way as every other secret.
- Full history. Every edit, move, copy, archive and restore is recorded per-secret and is itself encrypted.
Locked — only ciphertext exists.
Profiles — many vaults inside one
A single device can hold several independent identities. The master is the owner; each profile is a separate sign-in that can open only the specific secrets it has been granted — and it genuinely cannot decrypt the rest, because it does not hold the keys, not because a filter hides them.
- Drag-and-drop sharing: the master shares a secret into a profile by dropping it onto that profile's tab.
- Revoking access re-keys the affected secret, so a removed profile cannot use an old copy of the key it once held.
- A profile sees a clean tree assembled only from what it was granted — it never even learns the names of siblings it wasn't given.
Multi-device sync with no middleman
Your devices reconcile directly with each other. There is no relay, no cloud inbox, no "sync account."
- Changes converge automatically whenever two of your devices can see each other.
- Concurrent edits on different devices both survive — editing one field on your phone and a different field on your laptop merges cleanly rather than one clobbering the other.
- A delete made on one device stays deleted everywhere (it is a versioned change, not a local hide).
Three devices, one vault, always in agreement.
It finds a path, whatever the network
Two devices link up in the widest range of situations — but the vault only ever rides one of two radios: Wi-Fi or Bluetooth. LAN (mDNS), a raised hotspot and Wi-Fi Direct are all the Wi-Fi radio, and Apple's peer-to-peer link folds into the Wi-Fi rendezvous. There is no internet, no cloud relay, no NFC and no USB — if two devices can't reach each other over Wi-Fi or Bluetooth, they don't sync.
Found on the LAN via mDNS — the vault rides Wi-Fi.
The QR code is the one thing that isn't a transport: it's a bootstrap, not a data channel. When discovery fails — say mDNS is blocked on a locked-down network — one device shows a code carrying its address, a one-time binding secret (which replaces the six-digit check for that flow), and optionally a Bluetooth token or hotspot credentials. The scanner reads that and connects — still over the same Wi-Fi or Bluetooth radios, just without relying on discovery.
Adding and removing devices, safely
- Pairing uses a mutual short-authentication-string (SAS) check — a six-digit code compared on both screens — so a machine-in-the-middle cannot silently insert itself. The account's secret only crosses after both people confirm the digits match.
- The device you're adding pulls the whole vault down over the direct link and then unlocks it with the account password.
- Removing a device is enforced, not cosmetic. The remover signs a revocation with the account authority; the removed device, the moment it hears the verified notice, self-wipes — destroying its keys and vault so the copy it holds becomes useless. If it's connected, that happens in real time.
- An optional removal password gates the remove action across the account.
Deriving a code from the handshake…
Duress and disaster options
- Duress unlock. A separate secret opens a decoy vault — a plausible, generated set of secrets — while quietly wiping the real one. Under coercion, you can "open" your vault without giving anything up.
- Factory reset. A master-gated way to wipe the device back to a clean slate.
- Portable export. A versioned, self-contained encrypted export format, so you always have an off-ramp and a backup you control. (The v1.0 format is frozen — old exports will always import.)
Enter a code
To an onlooker, both codes look the same.
Details that matter for a security tool
- Theming & appearance are per-device: accent presets, a custom colour picker with alpha, light/dark, and backgrounds — cosmetics never sync as if they were secrets.
- Screenshot hardening on the platforms that allow it — the window is secure-flagged, so captures come back blocked.
- At-login decryption, not lazy: the vault is opened up front so the UI is instant, while large media stays lazy.
Part 2 · How it works
The technical details
Architecture at a glance
Canopy is built in three layers, deliberately separated so the security-critical core has no idea a UI or a radio exists.
- Pure Dart — no Flutter, no networking, no disk.
- Everything that turns a password and a secret into ciphertext lives here.
- It cannot leak, because it cannot reach anything.
- The mutually-authenticated handshake and the manifest→diff→merge protocol.
- Hybrid-signed identity and revocation.
- Also pure of UI — just the rules of talking between devices.
- On-device encrypted SQLite database.
- Every transport: Wi-Fi, Bluetooth, hotspot, QR.
- Knows nothing about how the crypto works — it just calls it.
The vault at rest
Every node (a secret, a folder) is sealed independently with its own key, usingXChaCha20-Poly1305 authenticated encryption. Payloads are padded before sealing so ciphertext length doesn't reveal secret length, and each node's ciphertext is bound to its identity so blobs can't be swapped. The key that protects everything is derived like this:
KEK = Argon2id(password, per-vault salt, cost) XOR hardwareSecret
- Argon2id is the memory-hard password hash (floor cost: 19 MiB, 2 passes, parallelism 1 — the OWASP minimum, stored per-vault so it can be raised over time). This is what makes a stolen vault expensive to brute-force.
- hardwareSecret is a 32-byte device-bound value kept in the platform keystore/keychain (DPAPI on Windows), never in the vault file. XOR-combining it into the key means the vault file alone is useless — an attacker needs both your password and that specific device's keystore.
Hover any stage to see what it does — or read the punchline below.
Grant-based access — the shape of the tree is a secret too
Canopy does not store "profile X can see folder Y." Instead, for every node a credential may open, there is a grant: a content grant carries the node's key, sealed to that credential's public key; a structural grant carries only placement (where the node sits and what it's called), also sealed to the credential.
A profile rebuilds its entire tree purely from its grants — it never reads a folder's child-list, because that would leak the names of siblings it wasn't granted. The database itself learns nothing about the tree's shape; it only sees opaque sealed blobs. Revoking access re-keys the node, so an old key a profile once held stops working.
Post-quantum: encrypted for a future that hasn't happened yet
This is the part that matters most for longevity. "Harvest now, decrypt later" is a real threat model: an adversary can capture encrypted data today and simply wait for a cryptographically-relevant quantum computer to break the classical maths. Canopy is built so that waiting doesn't help them.
Every place Canopy does public-key work, it uses a hybrid of a classical scheme and a NIST post-quantum scheme, combined so that breaking one is not enough — you'd have to break both.
Both schemes hold — your vault is sealed.
Hybrid encryption seals a key to a recipient with X25519 + ML-KEM-768. Confidentiality holds as long as either KEM is unbroken. Both ciphertexts and both of the recipient's public keys are mixed into the KDF salt, so a sealed box can't be replayed at a different recipient and neither ciphertext can be swapped — tamper with the post-quantum ciphertext and you get a different shared secret, which changes the key, which the AEAD rejects. The mechanism fails closed.
ephemeral = fresh X25519 keypair, one per seal
shared_x = X25519(ephemeral.private, recipient.x25519Public)
(ct_pq, s_pq) = ML-KEM-768.encapsulate(recipient.pqPublic)
key = HKDF( shared_x ‖ s_pq,
salt = ephemeral.public ‖ ct_pq
‖ recipient.x25519Public ‖ recipient.pqPublic )
sealed = ephemeral.public ‖ ct_pq ‖ XChaCha20-Poly1305(key, plaintext)Hybrid signatures authenticate an identity or a device revocation withEd25519 + ML-DSA-65 — forging one would require forging both. So a future quantum break of Ed25519 still cannot mint a valid device revocation, and a flaw in the newer post-quantum scheme cannot regress the classical guarantee we already have today.
signature = Ed25519_sign(msg) ‖ ML-DSA_sign(msg) // both cover the same message verify = Ed25519_verify(msg) AND ML-DSA_verify(msg) // accept only if BOTH pass
Sync: conflict-free by construction
Devices reconcile with a manifest → diff → merge round over a mutually-authenticated channel (both sides prove knowledge of the account secret via a BLAKE2b MAC before any data moves). Ordering across devices uses a Hybrid Logical Clock — a timestamp that stays correctly ordered even when devices' wall clocks drift. Merge is CRDT-style:
- Fields merge by id, each keeping the version with the newer clock — so different fields edited on different devices all survive.
- Children / folders merge the same way; a permanent delete is a tombstone that competes on the same clock, so delete-vs-re-add resolves by which happened later.
- The result is deterministic regardless of which side is "local," which is exactly the property that makes two devices converge on the same vault.
Two devices holding the same secret.
The device lifecycle
- Pairing runs an authenticated handshake with a transcript both sides hash in a fixed order; the six-digit SAS is derived from that transcript, so if the codes match, there is provably no machine in the middle. Only then does the account secret cross.
- Revocation is an account-authority-signed statement (hybrid-signed) that a device key is no longer a member. Peers refuse a revoked device and hand it its own revocation; the removed device verifies the signature andself-wipes. A sticky flag records the verified revocation before the wipe, so an interrupted wipe still refuses to reopen.
- Re-admission is handled: a device removed and later paired back in is recognised as current again (its fresh directory entry supersedes the old revocation).
A three-device account, sharing one vault.
Security properties, in one place
A note on honesty
Canopy leans on standard, published primitives — X25519, Ed25519, Argon2id, HKDF, BLAKE2b, XChaCha20-Poly1305 — and on NIST's post-quantum standards, ML-KEM-768 (FIPS 203) and ML-DSA-65 (FIPS 204), always in a hybrid with their classical counterparts so that adopting the new maths never means abandoning the old guarantees. Nothing here is home-grown cryptography; the design's job is to combine well-understood pieces so that the failure of any single one is survivable.
Your secrets live on your devices, the maths that protects them is bound to hardware you already trust, and when two of your devices are near each other they quietly agree on the truth — no server, no account, no one else in the loop.