Protocol · 01

Protocol overview

Miuchio is a shared photo album where the server stores the photos but is never given the keys to open them. Most of the cryptography is standard and I link to it rather than explain it again. These pages spend their time on the parts I designed myself: epochs, how keys reach members, the rules the server enforces, and how the database avoids recording who knows whom. New to the terms? Start with the Glossary.

What the design has to do

  • Photos are encrypted on the phone before upload. Only album members hold the keys.
  • A member can be offline for weeks and still receive every key they missed.
  • Someone who joins an album can see its whole history. This is a product choice.
  • Someone who is removed cannot open photos added after they left.
  • The server can enforce the album's rules without being able to read photos, names, album titles or album keys.
  • A copy of the database reveals as little as possible about who shares albums with whom.

For now an album has at most 10 active members and exactly one admin, its creator.

Who the design defends against

AdversaryWhat they haveWhat Miuchio aims for
Someone with a database copyEvery table, no server secretsNo photos, names or titles; no email addresses; coarse times. Memberships are not readably tied to accounts, but they can be grouped by person, and signatures link each album's admin to an unnamed account
A curious serverThe running server and its secretsNo photos, names, titles or keys. It can still see membership and upload metadata
A malicious serverEverything above, and it can lieCannot forge a signature under a key the phone already trusts. It can still substitute a key at first contact or during an invite, and withhold or hide things. Safety numbers are the check for substitution

Standard primitives, used as specified

None of these are mine, and I use them as their specifications describe. Read the links for how they work.

PurposeStandardImplementation in the app
SignaturesEd25519, RFC 8032libsodium
Key agreementX25519, RFC 7748libsodium
Key derivationHKDF-SHA256, RFC 5869Dart cryptography package
EncryptionAES-256-GCM, NIST SP 800-38DDart cryptography package
Keyed hashing (server)HMAC-SHA256, RFC 2104Go standard library

Known designs I adapted

These start from published designs, but the versions in Miuchio are my own, written on the primitives above:

PurposeBased onWhat is different in Miuchio
Offline key agreementSignal's X3DHSeparate signing and key agreement keys, the album ID in the derivation, and one handshake per delivery. The result wraps album keys once and is then discarded
Key verificationSafety numbers, from SignalMy own construction: one SHA-256 over both identity keys, sorted, and the album ID, so each shared album has its own number
First contactTrust on first useTwo kinds of pins, and first use allowed only when the phone holds no key for the album

The parts I designed

The ideas underneath are not new: group keys that change when membership changes, and small keys encrypted under other keys, are common. The specific rules, formats and checks below are mine, listed with the most important first.

PartWhat it doesChecked byPage
Epoch rulesA new album key only when someone is removed or leaves, never on a joinServerAlbums and epochs
Signed epoch changesThe admin signs the exact recipient list and every wrap; the server checks it against the live member set under the album lockServer. Members not yetAlbums and epochs
The removal freezeA flag stored in the same transaction as the removal; uploads are refused until the next epoch is committedServerAlbums and epochs
The signer checkWhich key may sign a delivery, decided from what the phone holds, not from the serverEach phoneTrust and verification
History on inviteOne handshake wraps every earlier epoch key; the joiner installs them in order and signs a receiptJoiner's phone, then serverAlbums and epochs
Per photo keys and upload gatesTwo keys per photo under the epoch key. Reserve and confirm both recheck the epoch and the freeze; a refused photo keeps its ciphertext and has its keys wrapped again under the newest keyServer, then the uploading phonePhotos, names and avatars
Changes to X3DHSeparate signing and key agreement keys, the album ID in the derivation, one handshake per deliveryEach phoneKey delivery
Member tokens and the user linkA random token per album; the account link is encrypted under server keys. Memberships still share one handleDatabase layoutMetadata and the social graph
Sealed names and avatarsNames carry their epoch in front; avatars are sealed separately per album and padded to one sizeEach phonePhotos, names and avatars
Recoverable account deletionDeletion runs one album at a time and resumes after a crash; a receipt stops the phone wiping itself for a deletion that was not acceptedServer and phoneAlbums and epochs
Sealed upload queue and cacheUploads survive the app being killed, and cached photos, names and pins are sealed under a device key read once each time the app startsEach phonePhotos, names and avatars

Three layers of keys

Handshake secretOne per deliveryWraps an album key once, then is overwrittenwrapsMK 0MK 1MK 2One album key per epochMembers keep every epoch key they were givenwrapsPhoto keyThumbnail keyTwo fresh keys per photoStored wrapped under that epoch's album keyencryptsencryptsPhotoThumbnailWhat the server storesCiphertext it has no key forOnly the top layer uses a handshake. Everything under it is plain AES-256-GCM with random keys.
Only the top layer involves another device. The album key and photo keys are random AES-256 keys.

Splitting it this way keeps the expensive part small. A handshake happens once per member when an epoch starts, or once per invite, never once per photo, and changing who is in the album never requires re-encrypting a photo.

One wire format

Every AES-256-GCM ciphertext starts with a version byte, and the version byte alone selects the algorithm. Names and avatar keys put a 4 byte epoch number in front of it, so the reader knows which album key to use:

ByteFormatUsed for
0x01AES-256-GCM: VER ‖ NONCE(12) ‖ TAG(16) ‖ CTKey wraps, thumbnails, files under 1 MiB, names, avatars
0x02ChaCha20-Poly1305Reserved, never written today
0x03AES-256-GCM in 1 MiB segmentsFiles of 1 MiB or more

The Go server and the Dart app share a file of known answer test vectors, so both sides must produce identical bytes for handshakes, signatures and hashes.

Reading order

  1. Identity and key delivery: the keys on each phone, and how an album key reaches another phone.
  2. Albums and epochs: creating, inviting, removing, and the signed changes the server checks.
  3. Metadata and the social graph: what the database records about people, and what it deliberately does not.
  4. Photos, names and avatars: per photo keys, the upload steps, and everything else sealed under an album key.
  5. Trust and verification: pins, the signer check and safety numbers.
  6. What Miuchio does not protect: tradeoffs and unfinished work in one place.