A self-hosted, post-quantum encrypted file store for iOS, built on the Cipher Station protocol. Your files live on a station you run. Nothing is uploaded in the clear, there is no company in the middle, and no account anyone can close.
Its sibling app, CipherFrame, shares the same pairing and crypto foundation and puts a photo/video feed on top of the same station.
| Feature | Where |
|---|---|
| Pair to a station by URL + 6-digit PIN | Views/Onboarding, ViewModels/OnboardingViewModel.swift |
| Pair to several stations and switch between them | Services/Storage/CredentialStore.swift, Views/Settings |
| Browse, upload, preview, and download encrypted files | Views/Files, ViewModels/FileBrowserViewModel.swift |
| Share individual files with specific followers | Views/Files/CipherShareView.swift |
| Follow other stations and read what they shared with you | Views/Shared, ViewModels/SharedFilesViewModel.swift |
| Show a QR code for your station; scan someone else's to follow | Views/Settings/StationQRCodeSheet.swift, Views/Shared/QRScannerSheet.swift |
| Recover a station whose address changed, via its IPNS record | Services/IPFS/IPNSRecordResolver.swift |
| Face ID / Touch ID lock | Services/Storage/BiometricAuthService.swift |
Files are encrypted on the device before upload. Each file gets a fresh symmetric key; that key is sealed into per-recipient envelopes with ML-KEM-768, and requests are signed with ML-DSA-65 (FIPS 203 / 204). The station stores ciphertext and fans out envelopes — it never receives a plaintext file.
Two things worth being straight about, because "post-quantum" invites assumptions:
- This is not Signal-style end-to-end encryption between users. Your station
recovers the symmetric key in order to re-seal it for a new device or a new
follower (
/rewrap). The threat model is "no cloud provider can read my files", not "not even my own server can". That is the point of self-hosting — but on managed hosting, the operator runs that station. - There is no forward secrecy. Device keypairs are long-lived, because the product's job is to open a file you saved two years ago on a phone you bought last week. A ratcheting protocol deliberately destroys that ability. The two goals are in direct tension and this app chose retention.
A station publishes a IPNS name (k51…) — permanent, derived from its own
key, and what you hand to other people. It also has a tunnel URL, which is
what a client actually connects to and which changes every time the tunnel
restarts. Clients resolve the IPNS name to discover the current URL, so nothing
has to be re-shared when it rotates. That is why the QR code encodes the IPNS
name and not the address.
IPNS records are fetched as signed records via delegated routing and verified
locally with Ed25519 (IPNSRecordResolver.swift), so a malicious router cannot
forge where your station lives.
Requires Xcode 15+ and iOS 17+.
brew install xcodegen
xcodegen generate
open CipherVault.xcodeprojThe project file is generated from project.yml; regenerate after adding
sources. Dependencies resolve through SPM on first build:
SwiftKyber,
SwiftDilithium, and
swift-sodium.
There is also a standalone test suite for the credential store and its multi-station migration, which runs without Xcode:
bash Tests/run-tests.shThe app is a client — it does nothing on its own. Run one from the cipher-station repo, then pair by entering its URL and the 6-digit PIN it prints.
Working software that one person uses daily, not a product. It has not had an
external security audit. Constants.clientName is "drive" — that string is
the client's bucket key in the station manifest, so changing it orphans files
already uploaded.