Technical overview

How ezkeep works

What happens on the network and on disk when you share a text, send a file or sync a vault. Short enough to read in ten minutes, precise enough to check.

Protocol v1 · app v1.0.0

01Overview

ezkeep is a peer-to-peer app. Every copy of it is both a client and a server: it announces itself on the local network, listens for HTTPS requests on TCP port 47575, and talks directly to the other copies it finds. There is no ezkeep server anywhere, and no traffic leaves your network.

Inside each ezkeep device: discovery, an HTTPS server and client, and local storage. Your phone Discovery · mDNS + UDP HTTPS server · :47575 HTTPS client · pinned Local files · vaults, transfers Your computer Discovery · mDNS + UDP HTTPS server · :47575 HTTPS client · pinned Local files · vaults, transfers announce every 3 s TLS · JSON · stream
Both sides run the same code. The client of one device calls the server of the other, and the reverse.

The app is written in Flutter and Dart, with one code base for Windows, macOS, Linux, Android and iOS. Networking uses the platform's own TLS stack through dart:io; vault cryptography uses PointyCastle.

02Finding devices

Two mechanisms run side by side, and their results are merged by each device's fingerprint (never by IP address, which can change):

  • mDNS / DNS-SD (all platforms): each device publishes the service _ezkeep._tcp with its fingerprint, name and platform in the TXT record.
  • UDP broadcast (desktop and Android): every 3 seconds a small JSON packet goes to port 47574 on each network interface's broadcast address. A device that hears a new peer answers it directly.
  • Add IP: for networks that block both, you type IP:port. ezkeep calls GET /api/v1/info and keeps the device until you remove it.
Discovery: periodic announcements over mDNS and UDP broadcast, and a direct reply. Pixel 8 MacBook Pro mDNS · _ezkeep._tcp · fp, name, platform UDP → 192.168.0.255:47574 · {"magic":"EZKEEP/1", "fp": …} UDP unicast reply · "reply": true Both lists now show the other device. It disappears about 5 s after its announcements stop.
Announcements repeat every 3 seconds. Your own announcements are recognized by fingerprint and ignored, and the IP always comes from the packet's source address, never from its content.

03Identity and trust

On first launch each device generates an EC P-256 key pair and a self-signed X.509 certificate, stored in the app's private folder. Its fingerprint is the SHA-256 of the certificate's DER bytes. The first six characters, in upper case, are the short code you see next to device names, like B8AAA9.

The same certificate is the device's TLS server identity. A device cannot present another device's fingerprint without that device's private key. Before every request, the client checks the certificate it receives:

Certificate pinning: the connection continues only if the certificate's SHA-256 matches the announced fingerprint. Discovery says fp = b8aaa96b… TLS handshake peer sends its cert Compare SHA-256(DER) == fp ? send reject POST /api/v1/clipboard X-Ezkeep-From: 0a298a3c… ← sender's fingerprint, on every request {"id": "…", "text": "…", "sentAt": "2026-10-07T17:41:00Z"}
A mismatch closes the connection with a "fingerprint mismatch" error. Verification is never switched off.

Every device on your network is trusted by default; there is no pairing step. You can block a device from its menu: the server then answers its requests with 403, and nothing is sent to it.

04Clipboard and chat

Clipboard text and chat messages are small JSON requests. The receiver answers 204 No Content as soon as it has stored the item. For chat, that answer is the "delivered" tick; opening the conversation sends a read receipt back.

Sending clipboard text and a chat message, with delivery and read receipts. Pixel 8 MacBook Pro POST /clipboard {id, text, sentAt} 204 copies the text, notice without it POST /chat/messages {id, text, sentAt} 204 ✓✓ delivered POST /chat/read {ids: […]} ✓✓ read
If the other device is unreachable, a chat message stays pending and is sent again, in order, the next time discovery sees that device.

Notices never include the received text, because it may be a password or a token. Text you copy from a password vault never enters the clipboard history and is never sent by Auto-share.

05File transfers

The sender only offers; the receiver pulls. After you accept, the receiver downloads each file in order with an HTTP Range request and writes it to a .part file. Data moves in 1 MB chunks, so even very large files never sit whole in memory.

File transfer sequence: offer, accept, ranged downloads, progress and completion. Sender Receiver POST /transfers (manifest) offer: Accept or Decline POST /status accepted GET /files/0 · Range: bytes=N- 206 · stream in 1 MB chunks append to video.mp4.part POST /status progress (~1/s) … next files, one at a time … POST /status completed check size, rename .part → final name
Either side can pause. To resume, the receiver asks for the bytes after the length of its .part file, so nothing is downloaded twice.
  • Pause and resume work across restarts: a transfer that was running when the app closed comes back paused, with the reason "App restarted".
  • Lost connection: 30 seconds without data, or a device that leaves the network, pauses the transfer with "Device offline". It never fails silently.
  • Safe names: file names are reduced to a base name, path separators and .. are removed, and name clashes get a (1), (2) suffix.

06Vault encryption

Each password vault has its own random vault key (32 bytes), made once when the vault is created. Your master password never encrypts anything directly and is never stored: it is stretched into a key-encryption key that unwraps the vault key.

Key hierarchy of a vault: the master password or the recovery key unwraps the vault key, which derives the records key and the auth key. Master password never stored Recovery key Emergency Kit · 160 bits Argon2id HKDF KEK Recovery KEK unwrap Vault key 32 random bytes · wrapped HKDF-SHA256 · salt = vaultId Records key "ezkeep-vault/1 records" Auth key "ezkeep-vault/1 auth" AES-256-GCM per item · fresh nonce HMAC: header MAC, sync proof
Changing the master password only re-wraps the vault key. Items are not re-encrypted, and the Emergency Kit keeps working.
PurposeAlgorithm
Master password → key-encryption keyArgon2id · 19 MiB · 2 iterations · 16-byte salt
Wrap the vault keyAES-256-GCM · 12-byte random nonce
Records key and auth keyHKDF-SHA256
Encrypt each item and folderAES-256-GCM · metadata bound as associated data
Header integrity and sync proofHMAC-SHA256 · constant-time compare
2FA codesTOTP · RFC 6238
  • Tamper-evident items. Each record's id, version, date, author and deleted flag are part of its encryption, so changing any of them makes decryption fail.
  • Key derivation off the main thread. Argon2id runs in a background isolate, so the screen never freezes while a vault unlocks.
  • Locking. Vaults lock after inactivity (5 minutes by default), after a maximum time open (8 hours by default), and optionally when ezkeep is hidden. Locking drops the decrypted data and clears a password still on the clipboard.
  • Biometrics (optional) keep the vault key in the platform's secure storage, and ask for the master password again at least every 7 days by default.

07Vault sync

Sync is manual: you pick the vaults and the devices, then press Sync now. Each pair talks once, and both sides end with the same records. The vault never travels decrypted, and a device must prove it holds the vault key before the other one merges anything.

Vault sync: the caller sends a proof and its encrypted records; the other device checks the proof, merges and returns the merged vault. Pixel 8 MacBook Pro POST /vault/sync {vaultId, proof, header, records} The receiver checks, in order: 1 · has this vaultId 2 · vault is unlocked 3 · proof = HMAC(authKey, "…|sync|from|to") merge record by record, save 200 {header, records, changed} merges the answer, saves: both now hold the same vault
Records are the encrypted blobs from the vault file. The proof binds both fingerprints, so it cannot be replayed to another device.
  • Conflicts: if the same item changed on both devices, the newest edit wins and the other one goes into the item's history, so nothing is lost.
  • First copy: to put a vault on a new device, open Receive a vault there and choose Send to a device on the other. The new device needs the master password to open it.
  • Clear errors: a locked vault, a missing vault or a different copy with the same id each produce their own message instead of a silent failure.

08What is stored

Everything stays in the app's private folder on each device. Nothing is uploaded anywhere.

DataWhere and how
Device identityPrivate key and certificate (PEM), created on first launch
Password vaultsOne encrypted JSON file per vault, written atomically with a backup copy
Received filesThe Downloads folder by default, changeable in Settings (iOS: the app's Documents, visible in Files)
Transfer stateOne small JSON file per transfer, so pause and resume survive restarts
Chat historyOnly if you turn on Keep chat history (off by default)
Clipboard historyThe last 20 items in memory, shown only if you turn on Show history

09Network and firewall

If devices do not see each other, allow these ports in the firewall of each computer:

PortProtocolUsed for
47575TCPHTTPS API: clipboard, chat, files, vault sync (falls back to a free port, which is always announced)
47574UDPBroadcast discovery (desktop and Android)
5353UDPmDNS, service _ezkeep._tcp

API endpoints

All under /api/v1, with JSON bodies and the X-Ezkeep-From header.

Method and pathPurpose
GET /infoFingerprint, name and platform
POST /clipboardReceive clipboard text
POST /transfersReceive a file offer (manifest)
POST /transfers/{id}/statusAccept, decline, pause, resume, cancel, progress
GET /transfers/{id}/files/{n}Download a file, with Range support
POST /chat/messagesReceive a chat message
POST /chat/readRead receipts
POST /vault/offerReceive a whole vault (Receive a vault screen only)
POST /vault/syncTwo-way vault sync with proof

Platform notes

  • iOS has no UDP broadcast and only answers while ezkeep is open on screen. Keep it in the foreground while receiving.
  • Desktop: closing the window hides ezkeep to the system tray, where Send clipboard is one click away.
  • Guest and office networks often isolate devices. Use Add IP when discovery is blocked but connections are allowed.

10Known limits

We prefer to be clear about what ezkeep does not do:

  • Same network only. There is no relay over the internet.
  • Blocking is a convenience, not a security boundary. Servers identify the sender by the fingerprint it declares in a header, which a hostile device on your network could fake. Your vaults do not depend on it: sync requires the vault key.
  • Clipboard and chat are protected by TLS in transit, not end-to-end encrypted like the vaults.
  • No autofill in browsers or other apps yet; copy and paste with automatic clipboard clearing.
helpQuestions about the design? Write to us through the contact link at the bottom of the page.