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.
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._tcpwith its fingerprint, name and platform in the TXT record. - UDP broadcast (desktop and Android): every 3 seconds a small JSON packet goes to port
47574on 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 callsGET /api/v1/infoand keeps the device until you remove it.
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:
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.
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.
- 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.
| Purpose | Algorithm |
|---|---|
| Master password → key-encryption key | Argon2id · 19 MiB · 2 iterations · 16-byte salt |
| Wrap the vault key | AES-256-GCM · 12-byte random nonce |
| Records key and auth key | HKDF-SHA256 |
| Encrypt each item and folder | AES-256-GCM · metadata bound as associated data |
| Header integrity and sync proof | HMAC-SHA256 · constant-time compare |
| 2FA codes | TOTP · 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.
- 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.
| Data | Where and how |
|---|---|
| Device identity | Private key and certificate (PEM), created on first launch |
| Password vaults | One encrypted JSON file per vault, written atomically with a backup copy |
| Received files | The Downloads folder by default, changeable in Settings (iOS: the app's Documents, visible in Files) |
| Transfer state | One small JSON file per transfer, so pause and resume survive restarts |
| Chat history | Only if you turn on Keep chat history (off by default) |
| Clipboard history | The 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:
| Port | Protocol | Used for |
|---|---|---|
47575 | TCP | HTTPS API: clipboard, chat, files, vault sync (falls back to a free port, which is always announced) |
47574 | UDP | Broadcast discovery (desktop and Android) |
5353 | UDP | mDNS, service _ezkeep._tcp |
API endpoints
All under /api/v1, with JSON bodies and the X-Ezkeep-From header.
| Method and path | Purpose |
|---|---|
GET /info | Fingerprint, name and platform |
POST /clipboard | Receive clipboard text |
POST /transfers | Receive a file offer (manifest) |
POST /transfers/{id}/status | Accept, decline, pause, resume, cancel, progress |
GET /transfers/{id}/files/{n} | Download a file, with Range support |
POST /chat/messages | Receive a chat message |
POST /chat/read | Read receipts |
POST /vault/offer | Receive a whole vault (Receive a vault screen only) |
POST /vault/sync | Two-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.