How Traypass encrypts a trip end to end: the keys, the format, how a key reaches each person, and what stays readable on our servers. Written from the app's code, for anybody who wants to check it.
LAST UPDATED 16 SEP 2026
The short version
Every key is made on a phone and kept in its Keychain. No key reaches our servers in a form we can use.
The private parts of a trip (stays, plans, seats, references) and every document are sealed with AES-256-GCM. A document only for you uses your own key. Everything shared uses the trip's key.
The trip's key reaches each person wrapped for their public key. It only reaches somebody new when somebody already on the trip taps Let in.
When somebody leaves or is removed, the key changes and everything shared is sealed again. Only somebody holding the key can change it, and a phone that holds the trip checks that they did.
A 60-digit code, the same on every phone, shows that nobody slipped a key in.
Three keys
Your account holds three kinds of key. The first is an identity, a Curve25519 key pair. Its private half never leaves your devices, and its public half is how other people hand you things. The second is your account key, a 256-bit AES key that seals what is only yours. The third is a trip key, one per trip, which seals what the people on that trip share. It has a version number, because it changes when somebody leaves.
Figure 1 · The three keys
Identity
Curve25519 key pair
MADE
On your iPhone, the first time
PRIVATE HALF
Your iCloud Keychain, never sent to us
PUBLIC HALF
On your profile and your row on each trip
FOR
Receiving trip keys from other people
Account key
AES-256
MADE
On your iPhone, the first time
LIVES
Your iCloud Keychain, and nowhere else
SHARED WITH
Nobody
FOR
Documents marked "Only me", and their titles
Trip key
AES-256, one per trip, numbered
MADE
On the creator’s phone, when the trip is made
LIVES
The Keychain of every member who was let in
TRAVELS
Wrapped for each member, on the trip
FOR
The trip’s stays, plans, seats, references and shared documents
Every key is made on a phone. The Keychain syncs them to your other Apple devices, end to end encrypted by Apple, so what you sealed on your iPhone opens on your next one.
All three live in the iOS Keychain with iCloud sync on. That is why what you sealed on your iPhone opens on the next one you sign in to. It also has a cost. If you lose every device and iCloud Keychain is off, nobody can open your "Only me" documents again, us included. What is shared can still reach you from somebody on the trip.
Sealing
Everything private goes through the same function, whether it is a 2 MB passport scan or the eleven letters of a booking reference. The content goes through AES-256-GCM with a fresh random nonce. The result is an envelope: a three-byte header, then the nonce, the ciphertext and the authentication tag.
Figure 2 · Sealing a document or a detail
boarding-pass.pdf
on your iPhone
AES-256-GCM
trip key, version 3
7f 3a c1 09 …
Envelope
what the server stores
HEADER · READABLE, AUTHENTICATED
AES-GCM BOX · CONTENT ENCRYPTED, ALL AUTHENTICATED
FORMAT
1 B
KEY VERSION
2 B
NONCE
12 B, not secret
CIPHERTEXT
size of the content
TAG
16 B
The header is not secret, but it is authenticated: GCM covers it as additional data, so changing the version number breaks the tag just like changing a byte of what is inside. The nonce is not secret, only never reused. A hotel address is sealed exactly the same way, only smaller.
The header says which version of the key sealed the envelope. After the key changes, not every envelope is sealed again at once, and a phone needs to know which key to try. GCM covers the header too, so it cannot be edited to point at another version.
The details of the trip
A trip is one document in our database, shared by its members. Each private field is sealed on its own and stored where the plain value would be. A stay's address, for example, is stored as pb1:followed by the envelope in base64. A location, which is a pair of coordinates, is sealed into a field of its own. A phone that holds the key opens each field as the trip arrives. A phone that does not shows "Locked", keeps every envelope exactly as it arrived, and the app does not let it add to the trip.
Documents
A document's scope decides its key. "Only me" uses your account key, and the trip uses the current trip key. The envelope goes to our own server, under your account or under the trip, and not to Firebase Storage. The server checks who you are and whether you are on the trip, then stores the bytes as they came. The document's title, its kind and the booking it belongs to are sealed with the same key and stored with the trip in our database, so "Ana passport.pdf" is never a readable filename anywhere.
Figure 3 · Where everything lives
Your iPhone
Private identity key
Account key
Trip keys, every version
Files and trip, readable
sealed
Traypass servers
THE TRIP · FIRESTORE, EU
Stays, plans, seats, references, as pb1:…
The trip key, wrapped once per member
Each member's public key
Document titles, kinds and bookings
Name, dates, stops, cover, flights
THE FILES · OUR OWN API
Envelopes, one per document
sealed
Ana's iPhone
Her own private identity key
The same trip key, once let in
Shared files, readable
Never your "Only me" files
A keyReadable, on a phoneSealedReadable on the server, on purpose
Keys stay on the phones. Everything that crosses the network, or rests on a server, is either sealed or listed under “Readable on our servers” further down.
Handing the key to somebody
A trip key is useless unless the people on the trip have it, and it must never pass through us readable. So each member gets their own copy, wrapped for their public key with HPKE (RFC 9180). The copies are stored on the trip, one per member, as 81 bytes that only that member's private key can open.
Figure 4 · Handing the trip key to Ana
Trip key
32 bytes, version 3
Ana's public key
from her row on the trip
HPKE · RFC 9180
X25519 · HKDF-SHA256 ChaCha20-Poly1305
info = "packbird.tripkey.v1:<trip>:3"
memberKeys["ana"] · 81 bytes, stored on the trip
FORMAT
1 B
ENCAPSULATED KEY
32 B, public
SEALED TRIP KEY
32 B
TAG
16 B
HPKE makes a fresh key pair for every wrap, agrees a secret with Ana's public key, and seals the trip key with it. Only Ana's private key can redo that agreement. The info string binds the result to one trip and one version, so a wrapped key copied onto another trip opens nothing. The encapsulated key is that one-time public key, and is not secret.
Joining a trip: Let in
An invitation link makes you a member of a trip. It does not give you its key. The key only arrives when somebody already on the trip approves you, and that step is manual on purpose. If a link leaks, whoever uses it sees what is not sealed (the trip's name, dates, stops, flights and who is on it) and nothing that is, and the people on the trip see a stranger waiting.
Figure 5 · Joining a trip
1
ANA
Opens the invitation link
Our API checks the token and adds her to the trip. It is the only place membership can grow. She sees what is not sealed, like the trip’s dates, stops and flights, and "Locked" wherever something is.
2
ANA’S IPHONE
Her public key goes on her row
Copied from her profile when she joins, and kept current by her phone. Being on the trip is not the same as holding its key.
3
YOU, OR ANYONE WITH THE KEY
Taps Let in
Your phone wraps the current trip key for Ana’s public key (Figure 4) and writes it on the trip. The server stores it and cannot open it.
4
ANA’S IPHONE
Unwraps the key and opens the trip
Her private key opens the wrapped key, the key goes into her Keychain, and every sealed field and shared file opens. Now you can compare the security code.
The database rules back this up. A wrapped key can only be written for a user who is already a member of that trip, and only our API can add a member, after checking an invitation token. They are checked entry by entry: only somebody holding the key adds a wrapped key for somebody else, nobody rewrites another person's entry except when the key changes, and only somebody holding the key can change it.
Public keys are kept apart from the member rows, in a list where only each person can write their own. A key is always wrapped for the key in that list, never for one somebody wrote next to a name.
When somebody leaves
Leaving a trip, or being removed from it, stops the server from giving that person anything new. That is not enough on its own, because their phone may still hold the key. So the key changes. The creator's phone, or another phone holding the key chosen the same way by every phone, makes a new one, wraps it for the people who remain, and seals the trip's details and the shared documents again with it.
Figure 6 · Luis leaves the trip
v3TRIP KEY, VERSION 3
Youholds key
Anaholds key
Luisholds key
Martawaiting
Luis's leaving marks the trip. The creator's phone, when it holds v3, makes a new random key, v4.
It wraps v4 for everyone still on the trip who held v3.
It seals v3 under v4 as the link. Every other phone checks it before taking v4.
It seals every shared document and every sealed field again under v4.
v3 stays in the Keychain of the phones that remain, so anything not sealed again yet still opens.
v4TRIP KEY, VERSION 4
Youholds key
Anaholds key
Luisremoved
Martawaiting
Marta was waiting to be let in and is still waiting afterwards. The new key goes only to the people who held the old one, so removing somebody can never let somebody else in by accident.
The new version carries the old key sealed under it, a link. Before a phone accepts a new version, it opens the links back to the version it already holds and checks they end at its own key. Nobody without the old key can make such a link, so a phone that holds the trip does not take a key made by anybody else, our server included. A phone that is made to forget a trip keeps the newest version it had, only to compare against, so the same check applies when it is let back in. A phone also refuses a trip set back to an older version than one it has held. The links only run backwards: a person removed at the change holds the old key and learns nothing about the new one.
One case no link can cover: a phone that has never held the trip, because it was just let in or lost its Keychain, has nothing to compare with and takes the key it is handed. That is what the security code is for.
No encryption can take back what somebody already saw. A person who was on the trip may have kept what they opened while they were there. What the new key guarantees is that nothing added or changed after they left opens with what they took.
Checking that nobody is listening
Everything above rests on one assumption: the public key on Ana's row really is Ana's. The row is stored on our servers. A compromised server could swap in a key of its own, receive the trip key the next time somebody taps Let in, and read along. End to end encryption cannot prevent that. It can make it visible.
That is what the trip security code is for, in the trip's menu under Security. It is a fingerprint of every member's id and public key, inspired by Signal's safety numbers. Each phone uses its own key from its Keychain rather than the copy on the trip, so a key swapped on the trip changes that person's code and only theirs. Everyone on the trip should see the same twelve groups. Scan the QR on the other phone, or read the numbers out loud. When a scanned code matches, everyone the code covers is marked as checked on your phone, and a person's mark goes away if their key changes. The mark is not tied to whoever showed the QR, because a QR cannot prove whose phone it came from.
Figure 7 · The trip security code
INPUT, MEMBERS SORTED BY ID
packbird.security.v1:<trip>
+ Ana's id + Ana's public key
+ your id + your public key
+ …
Every phone computes the code on its own, from the trip as it received it. If the server gave one person a different list of keys, their code would not match anyone else's. The digits above are an example.
The key version is not part of the code. Changing the key for the same people does not change who holds one, and a number that changed every time would teach people to ignore it. The code changes when somebody joins, leaves or gets a new key.
What is sealed, and what is not
We would rather say this plainly than let "end to end encrypted" suggest more than it covers. The trip is shared through a server so it can sync between phones, and a few things on it are readable on purpose.
Sealed
Every document, with its title, its kind and the booking it belongs to
The name, address, town, country and location of every stay
The title, place, notes, town, country and location of every plan
The stations of a train or bus, and their countries
Seats and booking references, flights included
The titles of the prep checklist and the names on the shared packing list
The notes the app writes about a booking
Readable on our servers
The flight
Number, airline, airports and their cities, times, status, gate, terminal, belt, check-in desk and aircraft. Our server watches them to tell you about a delay or a cancellation before anybody on the trip notices.
The face of the trip
Its name, dates, stops with their location and dates, and cover. Every invitation link shows most of it already, and someone waiting to be let in needs to know where they are going.
Who is on it
Member ids, names, photos and public keys, the wrapped keys and the links between key versions. The database rules are built on them.
The shape
Ids, and the dates and times of every booking and plan. A train or bus company and its number. Check-in and check-out times. Whether a plan, task or item is done, its due date and who has it. The type, size, date and uploader of each document. The forecast and the trip styles. A sealed field still shows that it exists and roughly how long it is.
Your own packing list
Private to your account by the database rules, not by encryption.
What else our servers see
The trip is not the only thing that leaves your phone. This is the rest.
Planning your days
The trip’s dates, its stops, the rough location of your stay (to about half a kilometre) and the suggested places already on your plan go to our server.
Checking a flight
The flight number and date go to our server, and from it to the flight data provider.
Covers
The name of each stop goes to our server to find a photograph.
Your profile
Name, @username, photo, home country, wardrobe answers and the places in your passport are stored readable, under your account.
Notifications
One row per phone: the push token, its language and time zone.
Apple
Place searches go to Apple Maps and forecast locations to WeatherKit.
Notifications
Our server sends the notifications, so it can only write what it can read. It writes "Marta added a stay" and sends two more things with it: the name of the stay, sealed, and the same sentence with a hole where the name goes. Before the banner appears, a small extension on your iPhone opens the name with the key in your Keychain and fills the hole. If it cannot, you get the server's sentence, which is true and one word shorter.
On your iPhone
You can ask for Face ID or your passcode before a document opens on your iPhone. This is a check in the app. The keys themselves are not tied to it.
A document opens into a temporary file that cannot be read while the phone is locked. The app deletes it when you close it, and every time the app starts.
Documents kept for offline use are stored as envelopes, never opened.
A booking you share into Traypass from Mail or Wallet is read on the phone, with Apple Intelligence, and the file never leaves it. A flight number found in it is checked with our server, and a hotel or venue name with Apple Maps. While it waits to be imported, the file cannot be read with the phone locked.
Home Screen and Lock Screen widgets show your stay and plans. What they read is sealed with a key that never leaves this iPhone, and is readable once the phone has been unlocked after starting up.
What this does not protect against
An unlocked, compromised phone. The keys are on it, as they must be for it to open your trip.
Signing out. The keys stay in your Keychain after you sign out, until you delete the account.
Somebody you let in. They can read the trip, which is the point of letting them in.
A key handed to a phone that has never held the trip, or a public key swapped by our server before somebody is let in. Only comparing the security code shows it. Compare it on trips where it matters.
A document shared and then made "Only me". Whoever already opened it keeps what they saw.
Metadata. Who travels with whom, when, and on which flights, as listed in the table above.
Losing every device with iCloud Keychain off. The "Only me" documents cannot be recovered.
The primitives
Content
AES-256-GCM, 12-byte random nonce, 16-byte tag, header as additional data
Identity
Curve25519 (X25519) key pair per account
Key wrapping
HPKE, RFC 9180, base mode: DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20-Poly1305
Key links
The previous key sealed under the next with AES-256-GCM, bound to the trip and version
Security code
SHA-512, 5,200 iterations, 60 digits in 12 groups
Key storage
iOS Keychain, synchronizable, accessible after first unlock
Library
Apple CryptoKit, on corecrypto. No cryptography of our own
Standards
CryptoKit runs on corecrypto, the cryptographic module Apple submits for FIPS 140-3 validation for each major release of iOS, and the newest release may still be in process. That validation is Apple's, not Traypass's. AES-256-GCM and SHA-512 are NIST-approved algorithms. X25519 and ChaCha20-Poly1305 are IETF standards (RFC 7748, RFC 8439) and are not on NIST's approved list. Traypass has not had an independent audit yet. When it has one, the report will be published here.
Reporting a problem
If you find something that does not add up, write to gonzalofuentessansalvador@gmail.com. Please give us a reasonable time to fix it before you make it public. The same address is in security.txt.