Pigeon

Security

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.

What the server stores

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.

What the server stores per message

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.

FieldValue
Message/File IDClient-generated ID
RecipientReceiving account ID (routing only)
CiphertextOpaque AES-256-GCM blob, server holds no decryption key
TimestampServer-set at time of receipt, not client-supplied
ExpirySet by chat retention setting
File sizeUsed to calculate usage limits based on your subscription plan

What the server cannot access

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.

RequestWhy
Message contentAES-256-GCM; key never transmitted to server
Who sent a messageSender identity is inside the ciphertext only — not a server-side field.
Which chat a message belongs toChat association is inside the ciphertext only — not a server-side field
Group membershipMessages 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 namesNever stored as plaintext — transmitted only inside encrypted envelopes
Display namesNever stored as plaintext — transmitted only inside encrypted envelopes
Encryption keysPrivate keys never leave user's device Keychain; chat keys are delivered only as AES-256-GCM ciphertext

Non-message data

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.

DataPurpose
Email addressAccount authentication
Device public keyEncrypting key deliveries to that device (public by definition)
Push tokenDelivering message notifications
Storage usageQuota enforcement
Subscription status & payment processor reference IDTo 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.

Key hierarchy

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.

Key derivation

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.

Message envelope format

Every message has two encryption layers. The server sees only the outer blob.

Inner layer (shared only between chat members)

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.

Outer layer (one per recipient device)

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.

Web app differences

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.

iOSWeb
Identity private keyiOS Keychain, biometric-protectedBrowser storage — no OS-level biometric protection
Chat keys at restDevice KeychainMemory only — not persisted; cleared when the tab closes
Message databaseEncrypted on-device SQLite (SQLCipher)No local database — messages live in memory for the session
Crypto engineAudited open-source cryptography libraryPlatform 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.

Implementation verification

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.

Change log

June 28, 2026 - reframed architecture while retaining technical depth.

May 24, 2026 - original publish date.

© 2026 Pigeon. All rights reserved. Built by Merandian.