How Deft Sync encryption works

Deft Sync is end-to-end encrypted. Your notes and their names are encrypted on your device with a key only your devices hold. The server stores what it is given and cannot read it. This page says exactly what that means, byte by byte where it matters, so it can be checked rather than taken on trust. Last checked against the app's code on 23 September 2026.

In short

We cannot read your notes or their names. Neither can anyone who takes the server. If you lose your twelve words and every device that holds the key, the synced copy is unreadable forever. Your files on your own disk are untouched. A leaked password cannot decrypt anything but can delete the synced copy or the account. The check at the end of this page covers the bytes the server stores. It does not audit the app.

What the server holds

For an account the sync server keeps the following. Like any server it also sees the internet address each request comes from, which it uses only to limit repeated attempts, the app version each device reports, and when each request arrives. Anything else it never receives.

Your account. Your email address, an Argon2id hash of your password and the name of each connected device: the kind of computer (“Mac”, “Windows PC”, “Linux computer”), never its hostname. Sign-in sessions and device tokens are stored as hashes.

Your files, encrypted. For each file: an encrypted copy of its contents (a "blob"), an encrypted record holding its name, path, size and modification time, and in the clear the blob's address, whether it has been deleted and a sequence number that orders changes. The server also sees how long each blob is, which roughly gives away the file's size. The same is kept for each earlier version for up to 30 days, fewer once your version history passes 1 GB, when the oldest go first.

A key check. A small fixed message encrypted under your key, so a device handed the wrong key finds out before it decrypts anything.

Pairing messages, briefly. When you add a device the server relays two messages between it and a device you already have, for at most ten minutes. It cannot open either.

What it cannot see. The contents of any file. The name of any file or folder. Your folder structure. Your encryption key in any form. Your password. Which of your files is which, beyond that one changed after another.

What it can see. How many files you have, how large each is and how well it compresses, roughly how long each path is (in steps of 256 bytes), when each changed, how often you edit and from which device, which files a device downloads or opens the history of, and when, which files hold the same content and when a file returns to content it held before (the blob address below is visible per row), and your email address. Any encrypted storage service sees the same list. What it could do with it is covered below.

The key

When you set up sync the app draws 128 random bits from your operating system. That is the whole key. It is not derived from your password, your email or anything you type. It leaves your devices only sealed to another device you approved. Each device keeps it in the operating system's credential store: the Keychain on macOS, the Credential Manager on Windows, the Secret Service on Linux. It is never written to a settings file.

The twelve-word Recovery Key is the same 128 bits written out in the BIP-39 word list so a person can copy it onto paper and type it back. The words are the key. No step turns them into one and no password is involved. Anyone with the words can read your notes. Without them or a paired device nobody can, us included.

From those 128 bits the app derives four working keys with HKDF-SHA256 and no salt (the zero salt of RFC 5869), using the labels deft-sync-v1:content, deft-sync-v1:blobid, deft-sync-v1:record and deft-sync-v1:keycheck. Each is 32 bytes and used for one thing only, so a message of one kind can never pass as another.

What is encrypted, and how

Everything stored on the server has the same shape. One version byte, currently 0x01. One flags byte. Both are bound into the encryption as associated data, ahead of anything else below, so neither can be changed without the message failing to open. A 24-byte random nonce. Then the ciphertext and its 16-byte authentication tag from XChaCha20-Poly1305 under a 256-bit key. That is the key strength of AES-256. We use this cipher rather than AES-GCM because its 24-byte nonce is safe to draw at random on every device with no coordination between them, and because it runs in constant time on any machine without hardware support. It is the construction libsodium recommends for random nonces. Before encryption file contents are compressed with zstd, but only if that makes them smaller. Flag bit 0 says whether they were. Records are never compressed (see below). The nonce is fresh for every message, so encrypting the same thing twice gives different bytes.

File contents are sealed under the content key. The blob's address on the server is an HMAC-SHA256 of the plaintext under the blob-id key, written as 64 hex characters. The HMAC is keyed, so the address says nothing about the content to anyone without your key. A plain hash would let a server check whether you hold a known file. The raw 32 bytes of the address are bound into the encryption as associated data, so the server cannot swap one blob's bytes under another's address without the app noticing, and the app also checks that what comes out hashes back to the address it asked for. Two identical files produce one blob, which is how sync avoids uploading the same attachment twice.

File names and paths live in a record: a small JSON document with the fields path, blob_id, file_uid (a random identity that stays with the file across renames and edits), deleted, counter (rises with every change), mtime and size. Before sealing, the JSON is padded with spaces to the next multiple of 256 bytes (at most 3,030), so a record's size says only roughly how long its path is; it is sealed without compression, which would squeeze the padding out. The record is sealed under the record key with the same label, deft-sync-v1:record, as associated data. The server keeps the blob address and the deleted flag beside it in the clear for bookkeeping; the exact size and the time are only in the record. The app treats the sealed record as the truth and refuses a row whose plain fields disagree with it.

The key check is 32 zero bytes, compressed like file contents (so its flags byte is 0x01), sealed under the keycheck key with the associated data deft-sync-v1:keycheck:<account id>:<key generation>, so a check copied from another account or an earlier key does not pass.

On the wire

Every request goes to https://sync.usedeft.com over TLS and plain HTTP is redirected there. TLS protects the sign-in, the device token and the bookkeeping fields the server holds in the clear. It is not what protects your notes. They are ciphertext before the connection opens, so a broken or intercepted connection exposes nothing the server itself could not already see.

How a device gets the key

From a device you already have. The new device shows a six-digit code. You type it into a device that is already syncing and that device sends the key across. The exchange is SPAKE2, a password-authenticated key agreement over the Ed25519 group with the code as the password. The server relays the two messages and learns nothing from them. The key travels sealed under the secret both devices agree on, bound to the request and both messages. A wrong code, mistyped or guessed by someone in the middle, gives a sealed key that opens nowhere and ends the request. One guess per request and a million codes. The code is never sent anywhere. It goes from one screen to the other through you. That is the one place the design relies on you. A pairing code only ever appears in the Deft app on the new device. If a web page, an email or anything else shows you a pairing code, do not type it into a device that holds the key. Approving it would hand the key to whoever made that request.

From the Recovery Key. With no device at hand you type the twelve words. The app checks their built-in checksum, then decrypts the key check for your account before it touches anything else. A wrong phrase is refused at once.

Replacing the key. If you think the words were seen, delete the remote vault from your account page and set sync up again from one device. That device draws a new key. The old ciphertext is gone from the live server at once, and from its backups after 30 days, and your other devices are asked to pair again.

Your password is not your key

The password signs you in to the account. The key encrypts your notes. The key is not derived from the password and the password never protects the key. A password reset is an ordinary email flow that changes no encrypted byte. A leaked password can do what the account page can do: delete the remote vault or the account, sign devices out, change the email and connect a new device. It cannot decrypt anything.

Device tokens, the credentials the app holds after signing in, can list, upload and download encrypted data. A stolen one can overwrite or delete the synced copies. The app holds a large batch of deletions for your confirmation rather than applying it, and your files on disk are never touched. Tokens cannot read a byte. They expire after 90 days without use and can be revoked one by one from your account page.

What a compromised server could still do

Read nothing. Not notes, not names, not the key. This holds for us, our hosting providers, a copy of the database and a court order. There is nothing to hand over that can be read.

Checking it

The description above is complete. The algorithms, the labels, the byte layout and the associated data are all named, so a decryptor can be written from this page alone. We publish one so it takes minutes rather than an afternoon.

What you need. Python 3 with the pynacl and zstandard packages. The verifier is one short file at github.com/usedeft/sync-verify. Read it before you run it. It talks only to sync.usedeft.com, reads your key without echoing it and never takes it as an argument or sends it anywhere.

The steps. Take two things from your operating system's credential store: the sync token the app signs in with, and your key, the 32 hex characters that hold the same 128 bits as your twelve words. Put the token in the TOKEN environment variable and run the verifier. It asks for the key, fetches your first note's record and blob from the server itself and checks them; give it a note's path to check that note instead. The README has the exact commands. The bytes come straight from the server, so the check does not rely on anything the app reports.

What it shows. One line per step. The record opens under the record key and names a path and a blob address. The blob opens under the content key with that address as associated data. The address is the keyed HMAC of what came out. Then the path and the size, and the contents if you ask. Change one byte of the file or give a different key and it stops with a plain message.

What it does not show. That the app never sends your key anywhere else. That takes an audit of the app. The only host the sync code talks to is sync.usedeft.com. A download of everything the server holds for your account, from the account page with nothing decrypted, is on the way.

Changes to the format

Every stored message carries a version byte. A record can gain fields but never lose or repurpose one. A device that meets a version it does not know refuses it rather than guessing. When the format changes this page changes with it and says so.

Where it runs

The sync server, its database and the encrypted blobs are all in Frankfurt, Germany. Backups hold the same tables and blobs as the live server, and keep what the live server deleted for 30 days. The privacy page lists the providers and what each holds.

Reporting a problem

If you find a problem with the design, the app or this page, write to support@usedeft.com.