Privacy & security
Client security
In an end-to-end encrypted system the client is the trusted component. Everything the protocol guarantees depends on the device holding the keys behaving correctly, so this page describes what the Keylane app does to earn that position — how your PIN becomes the key rather than a prompt, why the running app does not hold your identity keys in the clear, and what a forensic extraction of a seized device actually recovers.
Two locks, only one of which is ours
There are two separate locks on a phone, and conflating them is how on-device security claims usually go wrong. The device lock belongs to the operating system: your passcode, Face ID or fingerprint, and the filesystem encryption underneath. The app lock belongs to the application, and in most messengers it is cosmetic — the app draws a prompt over its interface while its keys stay live in memory. That is why a “locked” messenger can still decrypt a notification preview or connect an incoming call: it was never actually locked.
Keylane’s PIN is not a curtain over the interface. It is the input to the key derivation that produces the keys. A locked Keylane is not declining to show you your messages; it does not hold anything capable of reading them.
Identity keys and how they are stored
Your identity is a set of private keys generated on the device and never sent anywhere — not to our servers, not into iCloud or Google Drive, and not derivable from anything we hold.
- What is generated. At setup the app creates a Curve25519 identity key, a hybrid post-quantum key pair (X25519 combined with ML-KEM) used for the handshake and for request signing, and a 256-bit local storage key used for everything the app writes to disk. All of it comes from the platform cryptographically secure random generator —
SecureRandomon Android, the system CSPRNG on iOS. - At rest. The three keys live inside one encrypted profile blob. The key that opens the blob is derived from your PIN with PBKDF2-HMAC-SHA256, 600,000 iterations, against a 16-byte random per-account salt; the payload itself is sealed with XSalsa20-Poly1305. There is no second copy, no recovery file, and no server-side escrow — if the derivation does not produce the right key, nothing on the device opens it.
- Separate from the device passcode. The PIN is Keylane’s own secret and is never sent anywhere, so unlocking the phone does not unlock Keylane, and a platform-level bypass of the lock screen does not hand an attacker your keys.
The profile blob is protected by PIN entropy and 600,000 PBKDF2 iterations, and by nothing else. An attacker holding a copy of it works offline, where no attempt limit we could impose applies, and a numeric PIN hands them a small space to search: ten thousand candidates at four digits, ten billion at the ten-digit maximum. Thirty-three bits, at best.
It is worth writing down what that costs an attacker rather than leaving it at “use a long PIN”. On commodity hardware this derivation runs at roughly ten to twenty thousand candidates per second per high-end GPU, so four digits falls inside a second, eight digits inside a few hours, and even the full ten digits is on the order of a week for a single card — or about a day for eight of them. Rented by the hour, that is not an expensive attack.
Use the full ten digits anyway: six orders of magnitude is still the largest difference any single setting on this device makes. But the ceiling is a property of a numeric PIN, not of the derivation, and we would rather state it than imply a margin that is not there. Against an adversary who has copied your profile and can rent GPUs, the layer that actually holds is the hardware sealing described next, and that one requires the physical device.
While unlocked: keys wrapped by hardware
Deriving a key from the PIN puts your identity keys into memory, and memory is exactly what a live forensic capture takes. So the running app does not keep them there in plaintext. The moment the PIN check succeeds, the keys are re-encrypted under a throwaway key that only hardware can unwrap.
On unlock
- PIN and salt → 600,000 PBKDF2 iterations → account key → profile opens
- A fresh 256-bit session password is generated, never written to disk
- Identity, post-quantum, and storage keys re-wrapped under that password
- The password itself sealed by a Secure Enclave or Keystore key
Per operation
- Hardware unseals the password inside a scoped block
- One key, one operation, then dropped
- Between operations, memory holds sealed bytes and nothing else
On lock
- Sealed blob dropped, hardware key deleted outright
- Any copy that survives anywhere can never be opened again
- A fresh session password. On every unlock the app generates a new 256-bit random value. It is never written to storage and never leaves the process.
- Re-encryption. The identity key, the hybrid post-quantum key, and the local storage key are immediately re-wrapped under that session password. The copies wrapped under the PIN-derived key are dropped; the profile blob on disk is untouched.
- The session password itself goes into hardware. It is sealed with a key the app cannot read: an EC P-256 key generated in the Secure Enclave on iOS, used through ECIES, and a 256-bit AES-GCM key in the hardware-backed Android Keystore on Android. What the app holds afterwards is the sealed blob, not the password.
- Keys materialize only for one operation. Signing a request, running a ratchet step, decrypting a record — each one asks the hardware to unseal the session password inside a scoped block, uses the keys it unwraps, and drops them when the block ends. There is no long-lived plaintext key object for a memory scraper to find.
- Locking destroys the hardware key. Locking Keylane drops the sealed blob and deletes the hardware key entry outright —
SecItemDeleteon iOS,KeyStore.deleteEntryon Android. Any copy of the sealed blob that survives anywhere in memory, in a swap file, or in a crash dump becomes permanently undecryptable, including by us and including by the same device after it is unlocked again.
This defeats the offline half of a memory attack: a heap capture, a swap artefact, or a dump recovered after the app locked yields sealed bytes whose unwrapping key no longer exists. It does not defeat live instrumentation. While Keylane is unlocked, code executing inside the app process can ask the hardware to unseal on its behalf, exactly as the app does. Against a running, unlocked, actively instrumented app the honest answer stays the one in the threat model: not resisted.
Local storage, and the AFU problem
Forensic extraction tools distinguish two states of a seized phone, and the distinction decides what an examiner gets.
- Before First Unlock (BFU) — the device was powered off or rebooted and the passcode has not been entered since. The platform’s own filesystem keys are absent from memory and the device is close to a sealed box.
- After First Unlock (AFU) — you unlocked it once after boot and it has been in your pocket since, screen locked. This is the state a phone is in almost all of the time, and the state most seizures catch it in. The platform’s filesystem keys are resident in memory, so an exploit that gets past the lock screen reads decrypted files even though the screen was locked.
An app that leans on the operating system for at-rest protection inherits this. On Android, application data in Credential Encrypted storage stays mounted and readable once the device has been unlocked once; on iOS, the default class for app files — NSFileProtectionCompleteUntilFirstUserAuthentication — keeps them readable from first unlock onward. In the AFU state that is not much of a lock.
Keylane takes both platform mechanisms as far as they go and then does not rely on them. Message data on iOS is marked NSFileProtectionComplete, the class whose keys the system evicts when the device locks rather than at reboot. On Android the files sit in Credential Encrypted storage like everyone else’s, with our own AES-GCM record layer underneath, so extracting the platform’s CE keys yields ciphertext. Neither is the protection this page is describing; they are the floor beneath it.
Keylane therefore does not treat platform file encryption as its protection. It encrypts the content of its own records, with the local storage key, before handing anything to the database or the filesystem:
| On disk | State |
|---|---|
| Message bodies, quotes, and all message payload content | Encrypted per record |
| Contact names, local aliases, Keylane tags, delivery tokens | Encrypted per field |
| Attachment and media files, and their file names and descriptions | Encrypted on disk |
| Row structure: message and chat identifiers, timestamps, ordering, read and delivery status, unread counts | Visible |
| The Keylane identifiers of your contacts and their device identifiers | Visible |
| Per-device Double Ratchet session state | Visible |
The local storage key exists only in the form described above: wrapped under the session password, which is itself sealed by a hardware key that is deleted when Keylane locks. So the AFU case comes out differently here than it does for a conventional messenger. An examiner who defeats the lock screen while the device is in the AFU state, with Keylane locked, gets the database file and the media directory and can read neither — the key that would open the records is not on the device in any form, and the profile blob that could regenerate it needs your PIN.
It is worth being blunt about which of the scenarios on this page carries the weight. BFU is the state security discussions like to reason about and the state phones are almost never in. AFU is the state a phone is in for nearly the whole day, and it is the state a border inspection, a theft, or a warrant execution finds it in. Commercial extraction tooling is built around that fact.
Against a messenger that keeps its database key in after-first-unlock platform storage — which is the normal, reasonable design, and what most of the field ships — an AFU extraction produces the entire message history. Against Keylane locked, the same extraction produces a database and a media directory that cannot be opened: the key was destroyed on lock, and the only thing that regenerates it is a PIN that was never written down. That difference is the reason for the whole architecture above, and it is the one place where the design is not a variation on what everyone else does. A side-by-side against Signal, Matrix, Session and Threema is on the comparison page.
The honest counterweight is directly above and directly below: a copy of your profile taken away and attacked offline is bounded by PIN entropy rather than by this design, and the row structure around your messages is not encrypted at all.
The structure around the content is not encrypted, and this is the honest weak point of the design rather than a footnote. Someone holding the database can count your conversations, see when every message arrived and in which order, and tell which were outgoing.
More significantly, the Keylane identifiers of your contacts are stored in the clear even though the names and handles you gave them are encrypted — so an examiner who already knows a particular person’s Keylane ID can establish that you corresponded with them, and when, without reading a word of it. The stored Double Ratchet session state is unencrypted too; it does not open your saved history, which is under the storage key, but it is live protocol state.
We would rather list all of this here than let you find it in someone else’s audit report.
Why records, and not the whole database file
The obvious alternative to encrypting records one at a time is to encrypt the database file, which is what SQLCipher does and what Signal, Session and others use it for. It is a good, mature, widely reviewed design, and the comparison between the two approaches is not the one a vendor page usually makes: as cryptography they are equivalent. SQLCipher 4 encrypts each 4 KiB page with AES-256-CBC and authenticates it with HMAC-SHA512; Keylane encrypts each record with AES-256-GCM and seals the profile with XSalsa20-Poly1305. Both are sound authenticated encryption at 256-bit strength. Anyone telling you one of those two cipher choices is weak is selling something.
The difference is granularity, and it decides which of two very different attacks you are protected from.
Whole file
- Nothing readable without the key, structure included
- One key, held live for as long as the database is open
- Pages decrypt into a cache as they are touched and stay there
Per record
- Only what is on screen exists in plaintext, and only while it is
- The key materializes per operation, from hardware, then goes
- Row structure stays readable, because the engine still needs it
A file-level cipher is strictly better against the attack where someone holds your storage and no key: it leaves nothing legible at all, including the structure this page has just admitted Keylane leaves in the clear. That is a real advantage and it is the reason the honest weak point above exists.
Per-record encryption is better against the attack where the app is running. A file-level cipher needs its key for the lifetime of the open database connection, and the pages it decrypts accumulate in a cache — so over a session of ordinary scrolling, a large part of your history passes through memory in the clear, and a live capture of the process takes whatever is sitting there. Keylane never has a whole decrypted database to capture. The records on screen are decrypted; the key is unwrapped from hardware for a single operation and dropped; scrolled-past messages, closed media, and viewed documents are wiped from memory and from temporary storage as you leave them. What a live dump of an unlocked Keylane yields is roughly what was visible on the screen, rather than the archive behind it.
There is a second, less principled reason, and it belongs here rather than in a footnote: the same client code runs on Android, iOS, desktop, and in a browser, and a page-level cipher is a native dependency that does not go all four places. Choosing per-record encryption let one implementation cover every target. We would have made the same choice on the memory-exposure argument alone, but it was not the only argument.
The trade is therefore explicit rather than hidden: we accepted a readable row structure in exchange for never holding a decrypted archive. Whether that is the right way round depends on your adversary. If it is a forensic examiner working from a seized, locked phone, the structure is what they get and the content is what they do not. If it is malware on a phone you are using, the content is what matters and the archive is what we decline to leave lying around. The plan for closing the structural side — encrypting the ratchet state and replacing plaintext contact identifiers with blind indexes, so lookups still work but the column stops naming anyone — is real work rather than a promise here, and this page will describe it when it ships and not before.
When Keylane actually locks
Everything above depends on the app being locked, so it is worth being precise about when that happens.
- On demand. Locking from the app clears the session immediately: the sealed session password is dropped, the hardware key is deleted, and the decrypted-content cache the interface was using is emptied.
- On backgrounding — if you turn it on. Privacy & Security has a setting that locks Keylane the moment it leaves the foreground. It is off by default, because on by default means re-entering the PIN every time you glance at another app. If your concern is device seizure, turn it on; it is the single setting that most changes the outcome.
- Not on a timer. There is no idle auto-lock yet. Without the backgrounding setting, an unlocked Keylane stays unlocked until you lock it or the app is killed.
- Biometric unlock is a trade. Optionally your PIN can be stored wrapped by a hardware-held key so that a biometric check stands in for typing it. That means the PIN is present on the device in wrapped form rather than only in your head. On iOS release of that item is gated by a device-owner check enforced by the keychain itself; on Android the biometric prompt gates the app flow while the wrapping key is not itself bound to the check. If forensic extraction is in your threat model, leave biometric unlock off and type the PIN.
- Changing the PIN. A new PIN means a new salt, a fresh 600,000-iteration derivation, and the profile blob written out again under the new key. The old blob is replaced rather than kept as a fallback.
How long messages stay on the device
By default Keylane deletes local history older than 30 days, including the attachments belonging to those messages. The window is configurable from 24 hours up to a year, or off entirely, and there is an option to have your own outgoing messages deleted from the other side as well when the window passes.
This is separate from the relay’s 14-day limit, which applies to undelivered envelopes waiting to be collected and says nothing about your own history.
On-screen protection
- Privacy screen. Keylane obscures its content in the app switcher and in system screenshots of the task list, so conversations are not exposed in OS-level previews.
- Screenshot signalling. Platform limitations mean no application can reliably prevent a determined recipient from photographing their own screen. Keylane does not claim to.
- Notifications while locked carry nothing. This is not a preference — a locked Keylane cannot decrypt a message in order to preview it. The notification is a doorbell, and that is all it can be. See notification setup.
Push notifications
Waking a sleeping app requires going through Apple Push Notification service or Firebase Cloud Messaging. Keylane treats both as untrusted infrastructure.
No ciphertext and no sender information is ever placed in a push payload. The relay sends a generic wake-up signal; the app then connects directly to the Keylane server to fetch pending envelopes. Apple and Google learn that your device was woken, and nothing about the message or who sent it.
Network behaviour
The app talks to the Keylane relay and to the push provider for wake-up signals. It contains no analytics SDK, no crash reporter that transmits message content, no advertising identifier, and no third-party tracking library.
Traffic to the relay is carried over TLS in addition to the end-to-end encryption of the payload itself, so a network observer sees an encrypted connection to a Keylane server rather than the envelope structure.
An observer positioned on your network can see that you are connecting to a Keylane relay, and can see the timing and rough size of that traffic. Keylane conceals the content and the correspondents, not the existence of the connection.
If the device is compromised
A device under an attacker’s control while Keylane is unlocked is, from the protocol’s point of view, you. Code running with sufficient privilege can read decrypted messages as they are displayed and can drive the hardware unseal step itself, regardless of how well the keys are protected at rest. Sealing the session password in hardware raises the cost of an attack that arrives late or works from a captured image; it does not stop one that is present and running.
Practical mitigations, in rough order of value:
- Turn on locking when Keylane goes to the background, and use a ten-digit PIN. These two settings are worth more than everything else on this list combined.
- Keep the operating system updated — most real-world device compromise exploits known, patched vulnerabilities.
- Use a strong device passcode as well. It does not protect Keylane’s keys, but it is what stands between an examiner and the rest of your phone.
- Power the device off rather than just locking the screen if you expect it to be taken. A rebooted phone is in the BFU state, which is the strongest position the platform offers.
- Avoid sideloading builds from sources other than the official stores or our signed releases.
Verifying what you installed
Builds distributed through Google Play and the App Store are signed by Livotov Labs and verified by the platform before installation. The cryptographic core is published as Kodium under the Apache License 2.0, so the primitives described in the whitepaper can be inspected independently of the application.
The full client source has not been published yet. Until it is, we would rather say so than describe Keylane as an open-source application.