A technical overview of how we protect your content. Posts and photos are encrypted on your device before they reach our servers; the keys never leave your phone.
Everything you create on boopr is encrypted on your device before it reaches our servers.
Your private keys never leave your device. We rely on @noble/curves and @noble/ciphers on the client and the Go standard crypto package on the server. We don't write our own primitives.
The server only ever stores ciphertext. Decryption happens on your friends' devices, because we don't hold the keys.
boopr's encryption stops a specific list of attacks. Server-side compromise of post or message content. Passive surveillance by a network-level adversary or our own staff. Ad-tech and data-broker tracking of what you said, who you said it to, or what photo you sent. Casual snooping by anyone reading our database.
It doesn't try to stop everything. A compromised device reads your messages because the keys live on the device. A friend who screenshots a boop and forwards it on another app has done so manually; we notify the sender, but we can't undo it. Someone with physical access to your unlocked phone has the same access you do. Social-engineering attacks against you or your friends are out of scope. And we still hold metadata about who posted when and who pulled the post; under valid legal process, that metadata can be compelled even though the content can't.
The limits are part of the design.
Random content key per post, wrapped individually for each recipient via ECDH.
Profile data encrypted with key derived from signing key seed via HKDF. Version increments on friend removal, revoking access.
Images validated via magic bytes, re-encoded to JPEG (strips EXIF), resized, encrypted with XChaCha20-Poly1305 before upload. Server never sees unencrypted media.
24-word phrase (256-bit entropy) generated at signup. Ed25519 signing key derived from seed. X25519 encryption key derived via HKDF-SHA256. Profile key derived with version parameter for rotation. One phrase recovers everything.
iOS Secure Enclave (P-256 ECDH + AES-GCM key wrapping) and Android StrongBox/TEE (AES-256-GCM in KeyStore). 5-phase system: hardware KEK wraps a DEK, DEK derives MMKV encryption key and Keychain wrapping key via HKDF. Four sensitive Keychain services (identity keys, recovery phrase, Signal sessions, prekeys) additionally wrapped with XChaCha20-Poly1305.
On Android API 30+, hardware key operations require per-use biometric authentication via CryptoObject binding. Frida bypass of the UI gate is insufficient; the TEE/StrongBox itself enforces authentication. iOS uses Keychain access control with passcode requirement.
Identity keys are pinned locally on first contact. If a friend's identity key changes, the session is blocked until the user explicitly acknowledges the change. Prevents silent key substitution attacks.
Version increments on friend removal. New key derived via HKDF with updated version parameter, profile re-encrypted and redistributed. Old friends cannot decrypt new profile data.
P-256 ECDH self-agreement (iOS) or AES-256-GCM key (Android).
Key never leaves hardware. Not extractable, not exportable.
Derived via HKDF-SHA256 from hardware key agreement.
Exists only in hardware-protected memory during operations.
32-byte random value, wrapped by KEK, stored in Keychain/SharedPreferences.
Unwrapped into memory on app launch, cleared on background.
HKDF(DEK, "boopr-hardened-mmkv-key-v1") → 16-byte key.
Encrypts local cache (feed, connections, profiles).
HKDF(DEK, "boopr-hardened-keychain-wrap-v1") → 32-byte key.
XChaCha20-Poly1305 wraps 4 Tier 1 services: identity keys, recovery phrase, Signal sessions, prekeys.
setUserAuthenticationRequired(true) on KeyStore key.
TEE/StrongBox enforces auth per crypto operation.
UI bypass (Frida) insufficient — hardware validates HAT.
We retain only what we need to run the service. Raw IP addresses are never stored: they live in memory for rate-limiting and are cleared within 24 hours, never written to disk or logs. For abuse detection we store a salted HMAC of IP (not the IP itself), and the salt rotates monthly so the hashes can't be correlated across months. Encrypted backups can persist as ciphertext for up to 90 days after deletion (we still can't decrypt them). Push device tokens roll off after 90 days of inactivity. Subscription records stick around for three years after cancellation because California's Automatic Renewal Law requires it. Account data, profiles, posts, and media are removed within 30 days of account deletion.
Under valid legal process (subpoena, court order, or search warrant under ECPA and the SCA), we can hand over: account UUID, public keys, account creation date, the platform you're on, and the encrypted blobs we hold. We cannot hand over IP addresses (we don't store them), post content, captions, photos, profile fields, friend lists, or audience membership, because those are either never stored or encrypted with keys we don't have. Where law allows, we notify affected users; if a gag order applies, we don't.
A full encrypted-data export feature is on the post-launch roadmap. When it ships, the bundle will be encrypted with your recovery phrase and decryptable on a new device or independently if we ever shut down, using documented standard primitives (XChaCha20-Poly1305, Ed25519, X25519, HKDF) rather than a proprietary container. Until then, your 24-word recovery phrase already restores your account, posts, and profile on a new device.
Starting on the one-year anniversary of public launch (August 15, 2027), we'll publish an annual transparency report listing the number of legal requests received, the categories of data turned over, and the number of users affected. The full process and retention rules live in our Privacy Policy.
| Operation | Algorithm | Implementation |
|---|---|---|
| Signing keys | Ed25519 | crypto/ed25519 + @noble/curves |
| Encryption keys | X25519 | crypto/ecdh + @noble/curves |
| Symmetric encryption | XChaCha20-Poly1305 | golang.org/x/crypto + @noble/ciphers |
| Key derivation | HKDF-SHA256 | @noble/hashes/hkdf + crypto/hmac |
| Password hashing | Argon2id | golang.org/x/crypto/argon2 |
| Random generation | CSPRNG | crypto/rand + crypto.getRandomValues |
| Screenshot narc | ECDH + XChaCha20 | @noble/curves + @noble/ciphers |
| Recovery phrase | BIP39 (256-bit) | @scure/bip39 |
| iOS key wrapping | P-256 ECDH + AES-GCM | CryptoKit (Apple) |
| Android key wrapping | AES-256-GCM | AndroidKeyStore (StrongBox/TEE) |
We don't write our own crypto. The libraries above are the reference implementations maintained by their authors. We have not yet commissioned an external audit of how we use them; the surface is small (we don't custom-implement primitives), and we plan to commission a formal third-party review within twelve months of public launch.
Keys and nonces come from CSPRNGs: crypto/rand on the Go backend, crypto.getRandomValues on the client. Math.random never touches security-relevant code. Signature checks, HMACs, and TOFU identity comparisons run in constant time so an attacker can't learn bytes from response timing. Private keys and symmetric keys sit in Uint8Array buffers we zero out as soon as the operation finishes.
Photos are re-encoded on your phone before encryption, which drops GPS coordinates, camera model, capture time, and the rest of the EXIF block. The server never sees the original file. Push notifications carry a routing ID and nothing else: no sender name, no text, no thumbnail. Your device composes the notification text locally after it decrypts the payload.
Every HKDF derivation uses a distinct versioned info string (boopr-post-recipient-key, boopr-screenshot-narc-v1, and so on) so the same seed never produces the same subkey twice.
If you find a vulnerability, email [email protected]. PGP key on request. We acknowledge new reports within 48 hours and complete initial triage within 7 days. Critical issues we aim to fix within 7 days, high severity within 30, medium and low within 90.
In scope: the iOS and Android apps, the backend API and infrastructure, and this site. Out of scope: third-party services we depend on (Apple, Google, our hosting provider), social-engineering attacks against the team, denial-of-service attacks, and findings from automated scanning that don't show a real impact path.
We don't yet have monetary bounties; we're a small self-funded team. Reports that lead to a fix get public credit on this page (with your permission), boopr+ for life, and merch. Monetary rewards will roll in as the app's revenue does. We will never threaten legal action against good-faith research, and we don't enforce non-disclosure beyond a coordinated 90-day window.