Updated: June 27, 2026
This page is a technical disclosure of how Pigeon encrypts data, what the server stores, and what the architecture structurally prevents the server from accessing. The intended audience is privacy-conscious users, journalists, and practitioners.
Every message in Pigeon is encrypted with AES-256-GCM under a per-chat key that never leaves the sender's or recipient's device. The server stores only opaque ciphertext it cannot decrypt. Sender identity, chat association, group membership, display names, and encryption keys are not server-side fields — they exist only inside the encrypted payload. The only data structurally available on the server is account email address, ciphertext messages and/or files, device public keys (public by definition), push tokens, subscription plan, and storage usage counters.
The following tables describe what data the server holds, what it structurally cannot access, and what that means for any party — including Pigeon — attempting to query it.
The following table shows what data we store on our server for each message sent. We don't store the sender of any message or any chat details so we only know your account received an indecipherable (to us) message at a certain time but not who sent it or what chat it was sent from.
This structure applies the same for files sent, with the addition of file size to track your usage limits against your plan. All messages and files are stored in encrypted form and you hold the keys. We can't decrypt your content records stored on the server.
| Field | Value |
|---|---|
| Message/File ID | Client-generated ID |
| Recipient | Receiving account ID (routing only) |
| Ciphertext | Opaque AES-256-GCM blob, server holds no decryption key |
| Timestamp | Server-set at time of receipt, not client-supplied |
| Expiry | Set by chat retention setting |
| File size | Used to calculate usage limits based on your subscription plan |
The architecture prevents the server from accessing the following data. While the server knows an account exists and receives encrypted records, it does not know what those records contain, who sent them, what chat they belong to, or what users call themselves in those chats.
| Request | Why |
|---|---|
| Message content | AES-256-GCM; key never transmitted to server |
| Who sent a message | Sender identity is inside the ciphertext only — not a server-side field. |
| Which chat a message belongs to | Chat association is inside the ciphertext only — not a server-side field |
| Group membership | Messages are routed individually per device. The server sees individual deliveries, not group composition. Recipient account IDs are stored for routing but no explicit group structure exists on the server. |
| Chat names | Never stored as plaintext — transmitted only inside encrypted envelopes |
| Display names | Never stored as plaintext — transmitted only inside encrypted envelopes |
| Encryption keys | Private keys never leave user's device Keychain; chat keys are delivered only as AES-256-GCM ciphertext |
The server stores the following non-message data to provide the service, manage connected devices, and restore preferences on a new device.
Pigeon uses a zero-knowledge architecture so the server never holds the keys needed to decrypt your content. This structurally prevents reading your messages, deriving your social graph, running artificial intelligence on what you say, or sharing your contacts with advertisers.
| Data | Purpose |
|---|---|
| Email address | Account authentication |
| Device public key | Encrypting key deliveries to that device (public by definition) |
| Push token | Delivering message notifications |
| Storage usage | Quota enforcement |
| Subscription status & payment processor reference ID | To know what features or limits the user has access to. Billing and payment data is managed exclusively by our third-party payment processor(s) and is not stored on Pigeon servers. |
The server holds encrypted file blobs. The key that decrypts each file is wrapped with the per-chat key and stored only in the local on-device database — the storage provider never holds a key that can decrypt the blobs it stores.
Pigeon uses a layered key hierarchy rooted in a single master secret.
Master secret — Generated once on first install. Stored in the iOS Keychain with biometric protection. Never leaves the device or enters any network request. (iOS only; Android not yet available.)
Identity key pair — An asymmetric keypair derived from the master secret on demand. The private key is never stored separately. The public key is registered with the server so other devices can encrypt key deliveries to you.
Database key — Derived fresh from the master secret each time the app opens, then discarded. Never stored anywhere. Encrypts the entire local message database at rest.
Per-chat key (AES-256) — A random key generated for each chat. Stored in the device Keychain. Compromise of one chat key exposes no other chats.
Per-file key (AES-256) — A random key generated for each file. Protected with the chat key before storage; never stored in raw form.
The local database key is derived via HKDF-SHA256, not a password-stretching algorithm like PBKDF2. The master secret is generated from a cryptographically secure random source with full entropy — iteration stretching adds no security benefit when the input already has that property. The derivation is synchronous and completes instantly.
Chat keys and file keys are independently generated from a cryptographically secure random source — no derivation, fresh key per chat or file.
The shared secret used for key delivery between devices is processed through HKDF-SHA256 before use, ensuring the raw key agreement output is never used directly as an encryption key.
Every message has two encryption layers. The server sees only the outer blob.
The message content is encrypted with the shared chat key using AES-256-GCM. Each message uses a unique random initialization vector and includes an authentication tag that guarantees any tampering is detected. A key version field allows the app to handle re-encryption when a member is removed from a chat.
The inner layer is re-encrypted separately for each recipient device using that device's public identity key. Decrypting this outer layer requires the recipient's identity private key, which never leaves their device Keychain.
Decrypting the inner layer requires the chat key, which is delivered only inside an outer encrypted envelope — the server never holds it.
The web app uses the same AES-256-GCM / X25519 primitives and the same server protocol. The differences are in key storage as browsers have no hardware-backed Keychain.
| iOS | Web | |
|---|---|---|
| Identity private key | iOS Keychain, biometric-protected | Browser storage — no OS-level biometric protection |
| Chat keys at rest | Device Keychain | Memory only — not persisted; cleared when the tab closes |
| Message database | Encrypted on-device SQLite (SQLCipher) | No local database — messages live in memory for the session |
| Crypto engine | Audited open-source cryptography library | Platform Web Crypto API |
The server-side guarantees are identical across platforms: no plaintext, no sender identity, no group membership. The difference is on the client side: browsers do not offer hardware-backed key storage equivalent to the iOS Keychain. This is an inherent platform constraint shared by all browser-based E2EE applications.
Core encryption and key derivation paths are covered by automated tests that verify correct behavior and confirm that any tampering or derivation failure is detected and surfaced as an error before data is read or transmitted. No silent failures.
June 28, 2026 - reframed architecture while retaining technical depth.
May 24, 2026 - original publish date.
Make it yours
© 2026 Pigeon. All rights reserved. Built by Merandian.