The algorithms, key hierarchy and server-side data behind Pushany's end-to-end encryption. For an overview, see How encryption works.
Based on the implementation as of October 20, 2026.
Overview
Encryption and decryption happen on the device (the browser or the PC CLI) using the Web Crypto API. The server (Cloudflare Workers, D1, R2) only stores and relays ciphertext.
Encryption can't be turned off. Notes, tray items, checklists, tags and files are encrypted on the device before they're sent.
Ciphertext carries, as associated data (AAD), which item and which field it belongs to. If the server swaps in ciphertext from another item or field, the device can't decrypt it.
The ciphertext and key formats are checked against shared test vectors in two separate implementations (TypeScript and Rust).
Algorithms
Purpose
Algorithm
Summary
Content encryption
AES-256-GCM
12-byte IV, randomly generated for each encryption. The item and field are included in the AAD
Key derivation
PBKDF2-HMAC-SHA256
Derives keys from the recovery key and share link passwords. 16-byte random salt. 1,000,000 iterations for recovery keys you choose and share link passwords, 300,000 otherwise. Share link passwords must be at least 8 characters
Locked note PIN
PBKDF2-HMAC-SHA256 + HKDF-SHA256
Derives a key from a value made from the PIN with 1,000,000 iterations, the protection key and a random value held by the server
Sharing
RSA-OAEP
3072-bit, SHA-256. Hands list and note keys to invitees
Safety numbers
SHA-256
A 30-digit number derived from a public key, used to check that someone's public key hasn't changed
Agent writes
RSA-OAEP + AES-256-GCM
Content is encrypted with a new AES-256 key for each write, and that key is encrypted with your public key
Device approval
ECDH P-256 + HKDF-SHA256
Ephemeral key pairs on both devices produce the key that encrypts the keys being handed over. The 6-digit confirmation code is derived with SHA-256
Invite links
HKDF-SHA256
Derives a key from 32 random bytes in the URL
Passkeys
WebAuthn PRF + HKDF-SHA256
Derives a key from the passkey's PRF output
Passkey sign-in
WebAuthn (ES256, EdDSA, RS256)
The server verifies the passkey's signature. The challenge is 32 random bytes and single-use (10 minutes)
Recovery key
Random (120 bits)
24 characters in Crockford base32
Key hierarchy
Recovery key (only on the device)
├─ PBKDF2 ─→ encryption key (first version)
│ ├─ your items, tags, files, contact nicknames
│ └─ keys of checklists you haven't shared
├─ PBKDF2 (separate salt) ─→ protection key
│ ├─ new versions of the encryption key
│ ├─ keys of shared lists and notes
│ ├─ key pair's private key
│ └─ safety numbers you've seen
└─ PBKDF2 (separate salt) ─→ locked notes key (in case you forget your PIN)
PIN + protection key + server's random value ─→ locked notes key
└─ key of each locked note
Key pair's public key
├─ list and note keys given to invitees
└─ items written by agents
List / note key
└─ contents of checklists, shareable notes and locked notes
Key
How it's made and stored
Recovery key
A 120-bit string created by the app, or one you choose (12+ characters). Never sent to the server
Encryption key
AES-256. The first version is derived from the recovery key, and new versions are random. Stored on the device. The server keeps it only encrypted with the recovery key or the protection key, plus a value for checking that a key is correct
Protection key
Random AES-256. Stored on the device. The server keeps it encrypted with the recovery key
Key pair
RSA-OAEP 3072, one per user. The server keeps the public key and the private key encrypted with the protection key
List / note key
Random AES-256, one per checklist, shareable note and locked note. Stored encrypted for each person who uses it
Locked notes key
Random AES-256, one per user. Kept in the device's memory only while unlocked. The server keeps it encrypted with a key derived from the PIN, the protection key and a random value held by the server, a copy encrypted with the recovery key, and a copy for each passkey with passkey unlock turned on, encrypted with a key derived from its PRF output
Invite link secret
32 random bytes. Only in the part of the URL after #, which is never sent to the server
Share link key
Random AES-256, or derived from a password. Only in the part of the URL after # (the password isn't included)
Approval keys
Ephemeral key pairs created on both devices for each approval
Passkey
Derived from the passkey's PRF output. Stores the encryption key and protection key encrypted. The value given to PRF is the same for every passkey (so whichever passkey is chosen at sign-in, it opens them in one step)
While you allow an agent to read your content, the server holds your encryption key (see When the server can decrypt).
Renewing the encryption key
The encryption key has versions. When no agent is allowed to read your content anymore (you turned it off or it expired), and when you tap Renew in Settings, a device creates a new random version of the key.
The new version is stored on the server encrypted with the protection key, and your other devices and the CLI receive it from there. The protection key can't be derived from the encryption key, so someone with an earlier version can't decrypt the new one.
From then on, everything is saved with the new version. Contact nicknames, tags and the keys of checklists you haven't shared are redone right away. Notes, tray items and files move to the new version the next time they're saved. Earlier versions stay on the device to read what was encrypted with them.
The server doesn't accept writes encrypted with an earlier version.
Sharing
The contents of shared checklists and notes are encrypted with the list or note key. That key is given to each member encrypted with their own protection key or their public key, so the server can't decrypt it (for the exceptions, see When the server can decrypt and Scope and limitations).
When you start sharing — the app creates a new list or note key and re-encrypts the contents.
Inviting by @name — the inviting device encrypts the list or note key with the invitee's public key. The invitee's device recovers it with their private key and stores it encrypted with their own protection key.
Safety numbers — a 30-digit number derived from a person's public key. Your device remembers the safety number it first sees for each person and compares it with their current public key before giving them a key. If it doesn't match, the key isn't given. Remembered numbers are also stored on the server, encrypted with the protection key, so your other devices use them too. The sharing screen shows the safety numbers of the other people and your own. Your own number is derived from your private key.
Invite links — the list or note key is encrypted with a key derived from the secret after # in the URL and stored on the server. The server never receives the secret. Anyone with the link can join. Links expire after 7 days and can be revoked.
When someone is removed — the server deletes that person's key and stops returning the contents to them. The device that removed them creates a new list or note key, re-encrypts the contents and history, and gives the new key to the remaining members. Invites encrypted with the old key are re-encrypted, and invite links are deleted. Note attachment contents aren't re-encrypted: the old key is encrypted with the new key and stored with the file name and type. If a member leaves or deletes their account, or the key can't be recreated right away, the owner's, an admin's or an editor's device recreates it the next time it opens the list or note.
When ownership is transferred — the list or note key doesn't change. Members keep using their own copies of the key, and the previous owner stays on as an admin.
File share links — a copy of the file encrypted with a random key is stored on the server, and the key goes in the part of the URL after #. With a password, the key is derived from the password. Links expire after 7 days.
Locked notes
Each locked note's contents, history and attachments are encrypted with that note's key, and the note key is encrypted with the locked notes key. Each user has one locked notes key, which is kept in the device's memory only while unlocked.
The locked notes key is stored encrypted with a key derived with HKDF from a value made from the PIN with PBKDF2 (1,000,000 iterations), a value made from the protection key, and a random value held by the server.
The server holds a value for checking the PIN, made from the PIN and the protection key, and returns its random value and the encrypted key only when the check passes. After 10 wrong attempts in a row, locked notes are locked on all devices. The server can't decrypt the protection key, so it can't brute-force the PIN.
In case you forget your PIN, a copy encrypted with a key derived from the recovery key is also stored. Setting a new PIN with the recovery key also clears the lockout.
For each passkey with passkey unlock turned on, a copy encrypted with a key derived with HKDF from that passkey's PRF output is also stored. The value given to PRF is separate from the one for the encryption key, so the output used to unlock your data can't open it. The PRF output never leaves the device, so the server can't decrypt or brute-force it. Attempts are limited by the passkey provider (Face ID, fingerprint or device passcode).
Locked notes aren't shown to agents, the PC agent or Shortcuts, and they can't be shared. The encryption key held for agent read access can't decrypt them either.
Removing the lock re-encrypts the contents, history and attachments with the encryption key and turns it back into a regular note.
Approving a new device
A device that has the keys (A) hands the encryption key and protection key to a device without them (B). The server only relays the values and can't decrypt the keys being handed over.
When B asks for approval, your other devices with the app open show a confirmation screen with B's device name, device type, approximate location and the time.
When you start approving on A, the two devices exchange ephemeral public keys and derive a 6-digit code from them. The code is shown only on B.
You enter B's code on A, and A checks it on the device. If it matches, A encrypts the keys with a key derived through ECDH and sends them. If it doesn't, the request is deleted.
B checks that the keys it received are correct before storing them. The approval is recorded in your notifications.
The public keys are exchanged with a commit-then-reveal scheme, so if the server swaps them the codes don't match. The chance of swapping keys and still getting matching codes is 1 in 1,000,000 per request.
Requests expire after 10 minutes. The PC CLI is approved the same way.
Passkeys
A passkey added in Settings signs you in and delivers your encryption keys in one step. Accounts are created with Google, GitHub or X.
Adding — the passkey is created with a single-use challenge from the server, and the server verifies the response (attestation: none) before storing the passkey's ID and public key. Your encryption key and protection key are stored encrypted with a key derived from the PRF output. The response the device sends never includes the PRF output, and the server rejects it if it does.
Signing in — you pick a passkey from your provider without typing a name. The server checks the challenge, origin, rpId, user verification (UV), signature and signature count, and that the user ID returned by the passkey matches its owner. Each sign-in gets a new sign-in token.
Receiving keys — the PRF output comes back in the same step as the sign-in, and the device opens the encryption key and protection key with it. The PRF output is kept only in the device's memory and is cleared once used, after 2 minutes, or on sign-out. Using another device's passkey with a QR code can return a different PRF output; in that case only the sign-in happens.
Removing — removing a passkey also ends the sign-ins made with it on the server (the device you removed it from stays signed in). Adding a passkey and signing in with one are recorded in your notices (device type and approximate location).
What the server stores
Stored encrypted
Body, title, URL, tags, file names and types, file and attachment contents, list names, colors, icons, due dates and notes, section names, item text and notes, contact nicknames, safety numbers you've seen, tag definitions, and the text in note and list history.
Stored in plain text
The following isn't encrypted, because the server uses it for sorting, filtering, notifications and sharing.
Object
Stored in plain text
Items
Type (note, link, file, clipboard), format (Markdown, list), memo flag, created / updated / edited / pinned / bookmarked / trashed times, tray expiry, sticky note color, file size, sending and target devices, content flags (whether it contains text, links, images or videos), number and size of attachments, whether it's locked
Checklist items
Checked time, position, flag, priority, section, due date and time, reminder settings, repeat, created and updated times
Reminders
Time, repeat, time zone, notification language, target devices
Sharing
Members, pending invitees, roles and @names; for history, the kind of action and who did it when
Agents
Names of lists chosen under "Lists shown by name" and of shared lists with "Show name to agents" on, the agent activity log (kind of action and target IDs), names of authorized agents
Account and devices
@name, an ID per sign-in method, time zone, storage used, device names and types, last used times, API key names, passkey names and public keys, signature counts, whether passkeys sync, the encryption key version, the locked note PIN type and the number of wrong attempts in a row, the type and approximate location of a device asking for approval
Notifications (Web Push)
Only the item ID and type are sent. The device fetches the content and decrypts it
For connection logs and other records, see the Privacy Policy.
The server and operator can't see
The content listed under "Stored encrypted"
Your recovery key
Your encryption key (except as below)
Your protection key, private key, and list and note keys (except as noted below)
Your locked note PIN and locked notes key
The part of invite and share link URLs after #
The server and operator can see
Everything under "Stored in plain text"
Ciphertext sizes, and when and how often you write
Who shares what with whom
The content described under "When the server can decrypt"
When the server can decrypt
Allowing an agent to read your content — when you allow it for any agent, your device sends the encryption key (every version) to the server. The server stores it encrypted with a server-side secret key, and decrypts it each time an agent reads or writes. During this time, the operator holds your encryption key. Read access turns off automatically when an agent doesn't use it for 14 days, or 90 days after you allow it. When no agent is allowed to read your content anymore, the server deletes the key and a device renews the encryption key.
While this is on, the server can decrypt anything encrypted with the encryption key (notes, tray items, files and checklists you haven't shared, tags and contact nicknames). This key can't decrypt shared lists and notes, or locked notes.
"Allow agents to read contents" for shared items — when the owner or an admin turns it on for a shared list or note, the device that turned it on sends that list's or note's key to the server. The server stores it encrypted with a server-side secret key, and decrypts it each time the agent of a member who allows access to contents reads or writes. During this time, the operator holds that list's or note's key and can decrypt its contents, history and attachments. Agents encrypt what they write with the list or note key. It turns off automatically when agents don't use it for 14 days, or 90 days after it was turned on. When it turns off, the server deletes the key and a member's device recreates the list or note key. Every member is notified when it's turned on or off, and a badge appears at the top while it's on.
Items written by agents — the server encrypts them with your public key as soon as it receives them and doesn't store the plain text. Your device takes them in when you open the app. What you tell an agent stays with the agent.
Lists shown by name to agents, and "Show name to agents" for shared lists — the names of the lists you choose are stored in plain text so that agents can refer to them. For a shared list, the name is stored only when the owner or an admin turns the setting on, and then every member's agents can refer to it. Every member is notified when it's turned on or off, and a badge appears at the top while it's on. Turning it off deletes the plain-text name.
Shortcuts — items sent from Shortcuts reach the server unencrypted. As soon as the server receives text or a URL, it encrypts it with your public key and doesn't store the plaintext. Your device picks it up when you open the app and re-encrypts it with your encryption key. If your key pair hasn't been created yet, it's stored as plaintext and your device encrypts it later. Files aren't encrypted. The PC agent also uses an API key, but it encrypts on the PC before sending.
Items saved before you turned on encryption — text, links and note history are encrypted by a device with your encryption key when you open or edit them, and a little at a time while the app is open. Files and attachments aren't encrypted.
Contact messages and reports — these are meant for the operator to read and aren't encrypted.
What's stored on the device
The encryption key (every version) and the protection key are stored in the browser's IndexedDB. They're extractable so they can be handed to another device during approval. The locked notes key isn't stored; it's kept in memory only while unlocked. The contents of locked notes aren't stored in the search index either.
Search runs on the device. The search index (decrypted bodies, titles and so on), items waiting to be sent while offline, cached lists and drafts are stored on the device in plain text.
When a different user signs in, or the session ends, these are all deleted.
On a device with "Don't save keys on this device" turned on (for shared computers), the keys above, the search index, pending items, cached lists and drafts, along with remembered safety numbers, recently used tags, share links you created and your last account info, aren't saved on the device. They're kept only in the open tab's memory. They're gone when the tab is closed, so each time the app opens, the keys are received again through approval, a passkey or the recovery key. The service worker can't read the keys, so notifications don't show contents. The only thing saved on the device is that the setting is on. The sign-in cookie has no expiry (the browser deletes it when it closes), and the server ends the sign-in 12 hours after it was last used. Turning the setting on also issues a new sign-in token.
The PC CLI stores the API key and keys unencrypted in its config file, readable and writable only by you.
Scope and limitations
Swaps that aren't detected — modified ciphertext and ciphertext swapped in from another item or field are detected. Rollback to older ciphertext in the same field, deleting or reordering items, swapping tag definitions with each other, and reordering attachments within the same note aren't.
Ciphertext without AAD — ciphertext saved before AAD was introduced can be decrypted in any field. While any of it remains under the same key, swapping it in for another field's ciphertext isn't detected. The app re-encrypts it with AAD when you open an item and gradually in the background. Contents of files added before then aren't re-encrypted and stay without AAD.
The public-key format — items written by agents and items sent from Shortcuts are encrypted with your public key, so the server can write any content into checklist items and into tray items and notes.
Brute-forcing the recovery key — anyone with the server's data can try to brute-force the recovery key offline. With the 120-bit key the app creates, that isn't realistic. A key you choose is only as strong as the key itself. It must be at least 12 characters, common passwords are rejected, and PBKDF2 runs 1,000,000 iterations.
Locked note PINs — the limit on attempts comes from the server's check. Someone who obtains both the protection key on your device and the server's data can brute-force the PIN offline. A 6-digit PIN can be tried within a realistic time. With passkey unlock turned on, anyone who can use the passkey (including someone who knows the device passcode) can open locked notes without the PIN. When you add a lock to an existing note, its earlier ciphertext stays in backups.
Passkeys — anyone who can use a passkey (including someone who knows the device passcode) can both sign in and unlock. Someone who has taken over your sign-in can add a passkey and sign in with it. Adding one is recorded in your notices, and the passkey list appears after you sign out of other devices, so remove any passkey you don't recognize.
Invitee public keys — the public keys used for invitations come from the server. If one doesn't match the safety number seen before, the key isn't given, but a key that was already swapped the first time it was seen can't be caught. The server can delete remembered numbers, so the same applies to a new device or after signing in again. Comparing safety numbers with the other person reveals a swapped key.
What device approval checks — entering the code prevents the server from swapping public keys and prevents approval by someone who hasn't seen the code. It doesn't help if someone who has taken over your sign-in tells you B's code and you enter it. The device name and type are values B sends and can be faked. The location is an estimate from the IP address.
Delivered code — as a web app, the code that runs on your device is delivered by the server. An attack where the server delivers modified code can't be prevented.
Renewing keys — changing your recovery key doesn't change the encryption key or the protection key. The encryption key can be renewed, but existing data isn't re-encrypted. There's no way to renew the protection key.
After agent read access ends — the weekly database backups (kept for 90 days) and D1 Time Travel (7–30 days) keep a copy of the key from before it was deleted. With such a copy and the server-side secret key, data saved before you turned it off, and data saved before a device renewed the key, can be decrypted.
After "Allow agents to read contents" is turned off — the backups and Time Travel keep a copy of the list or note key from before it was deleted. With such a copy and the server-side secret key, contents saved before it was turned off, and contents saved before a member's device recreated the key, can be decrypted. Attachments aren't re-encrypted when the key is recreated, so attachments that existed before it was turned off can be decrypted too.
Settings chosen by other members — the owner or an admin can turn on "Allow agents to read contents" without the other members' consent. Members are notified and can leave, but contents stored while it was on could be decrypted on the server.
After removing a member — if the key couldn't be recreated right away, the old key stays in use until the owner's, an admin's or an editor's device next opens the list or note, so a removed person who obtains the ciphertext can also decrypt what was saved in the meantime. Note attachments stay encrypted with the old key, so a removed person who obtains the ciphertext can decrypt attachments that were there before they were removed. Attachments added afterwards are encrypted with the new key.
On the device — keys stored on the device are extractable, so malware or developer tools on the device can extract them. The search index and pending items are also in plain text on the device. Even with "Don't save keys on this device" on, the keys are in memory while the tab is open. The sign-in cookie stays until the browser is closed. A browser set to reopen the pages you had open, or the app added to the Home Screen on iPhone or iPad, may keep it after closing. Passwords and form entries saved by the browser stay on the device.
Metadata — as listed under Stored in plain text, times, counts, sizes, who you share with and so on can't be hidden.