Encryption & security · v2 ciphertext format · zero-knowledge

Your password.
Your phone.
Your data.

Vaultneur encrypts every document, every record, every field on your device — before any byte touches our servers. We hold no key that can decrypt your vault. Not because we promise; because we built it that way.

AES-256-GCM Zero-knowledge
AES-256-GCM
On-device
Authenticated encryption, 12-byte IV per blob
Zero-knowledge
By design
Supabase only ever sees opaque ciphertext
310,000 iterations
PBKDF2 · SHA-256
OWASP-recommended work factor
Threat model

The honest version: assume our servers are hostile.

Most "secure" apps trust their own backend with the keys. We don't. Vaultneur is built so that if our database were leaked tomorrow, an attacker would see ciphertext blobs, file sizes and timestamps — nothing else. Your password is the only thing that can derive the key that unwraps your vault, and your password never leaves your phone. We can't decrypt your data, even under subpoena.

How a document upload works

Six steps. Five of them happen on your phone.

Every file you save runs through the same flow. Encryption finishes before the app makes a single network request.

01

You unlock the vault

Face ID, Touch ID or your password unlocks the vault key from the device's secure storage slot (WHEN_UNLOCKED_THIS_DEVICE_ONLY).

02

The app reads the file

Picked from camera, gallery or a PDF picker, then loaded into memory as a base64 string. Nothing has left the phone yet.

03

AES-256-GCM encrypts the content

A fresh 12-byte IV is generated, then the base64 is encrypted with the in-memory vault key. Output: v2::.

04

Filename & MIME are encrypted too

The filename and content type are run through the same cipher, with their own IVs. Only the file type in the records table stays plaintext.

05

Blob is uploaded to Supabase Storage

The encrypted body is uploaded as application/octet-stream to encrypted_documents/{user_id}/{random_id}.enc.

06

Metadata row is inserted

Encrypted filename, encrypted MIME, file size and timestamps are written to the documents table. RLS enforces auth.uid() = user_id.

Your device Encrypt + base64
v2:: · TLS 1.3
Supabase Storage + Postgres
Vault key never sent Password never sent Filenames encrypted Download reverses it locally
The vault key system

Three layers of keys. None of them on our servers in usable form.

The heart of the security model. Read top-to-bottom — each layer wraps the one above it.

01 In-memory only

Vault key AES-256 · 32 bytes · random

The actual key that encrypts every document, every record, every field. Generated once on signup using cryptographically-secure randomness, then stored in the OS keychain with the WHEN_UNLOCKED_THIS_DEVICE_ONLY access flag while the vault is open. Cleared from memory the moment the vault locks.

  • iOS Keychain / Android Keystore
  • Never sent to Supabase in plaintext
wraps
02 Derived on unlock

Password-derived key PBKDF2-SHA256 · 310,000 iterations

Derived from your password + email (lowercased, trimmed, used as the KDF salt). This key never encrypts your data directly — it encrypts the vault key. The wrapped vault key is stored both on-device and in Supabase, so you can unlock on a new phone with just your password.

  • Salt = your email
  • Change Password re-wraps only this layer
or — optionally — wrapped by
03 Optional

Biometric key Secure Enclave · Face ID / Touch ID

If you opt in, the raw vault key is also stored in a separate keychain slot gated by Face ID, Touch ID or fingerprint. Lets you unlock without typing your password. Wiped on sign-out, on biometrics-disabled, or when system biometrics enrolment changes.

  • Hardware-bound, per device
  • Cleared automatically on sign-out
Algorithms

Boring, vetted, peer-reviewed cryptography.

Nothing exotic. Every primitive is industry-standard and recommended by NIST, OWASP or active RFCs.

Symmetric encryption
AES-256-GCM

Authenticated encryption for files, filenames, MIME types and every record field. 256-bit key, 12-byte IV per blob, 128-bit auth tag.

Used for every byte of your vault data.
Key derivation
PBKDF2-SHA256

310,000 iterations — the OWASP 2023 floor for SHA-256. Salt is your lowercased, trimmed email. Derives the password key that wraps your vault key.

Used to bootstrap the password-derived key.
Randomness
getRandomBytesAsync

All IVs, vault keys and recovery passphrases come from the platform's CSPRNG via Expo crypto — backed by /dev/urandom on Android and SecRandomCopyBytes on iOS.

Used for every fresh secret generated.
Recovery
BIP-39 style · 24 words

A 24-word passphrase from a curated wordlist. Used as a PBKDF2 input with a fixed domain salt (vaultneur-recovery-v2) to derive an alternate wrap key.

Used to restore your vault if you lose your password.
Verification & signing
SHA-256

Used to hash your recovery key for verification (we never store the recovery key itself), and to fingerprint your hand-drawn signature on signed documents.

Used for recovery verification + document signatures.
Transport
TLS 1.3

Modern TLS on every Supabase request, with HTTPS-only pinning at the platform level. No fallbacks to broken curves, ciphers or old versions.

Used for every network request the app makes.
Where the crypto actually runs. Web Crypto API (crypto.subtle) via Hermes on the React Native new architecture. On older devices, the app transparently falls back to expo-crypto + CryptoJS. Legacy AES-CBC ciphertexts (from earlier app versions) are auto-detected and decrypted seamlessly — the v2: prefix is what tells the app which path to take.
Ciphertext format

Every encrypted value is just a string.

No exotic container formats. Three colon-separated parts, base64-encoded, version-prefixed for future-proofing.

stored value documents.encrypted_filename
v2:3qK0/9aBcdEf12Gh:QmlnT3BhcXVlQ2lwaGVydGV4dEdvZXNIZXJlPT0=
v2

Format version. Tells the app to use AES-256-GCM. v1 and unversioned strings are decrypted as legacy AES-CBC.

IV

12 random bytes, base64-encoded. Fresh per encryption — never reused across two blobs, even for the same field.

Ciphertext + tag

The encrypted payload plus a 128-bit GCM authentication tag. Tampering with a single bit fails decryption.

What's encrypted

Field by field, byte by byte.

Two object types live in your vault: documents and records. Here's exactly which parts are ciphertext before they leave your phone.

Documents

Files in Supabase Storage.

  • EncryptedFile content (base64)
  • EncryptedFilename
  • EncryptedMIME / file type
  • EncryptedSignature image (if signed)
  • PlaintextFile size · timestamps · user ID
encrypted_documents/{user_id}/{random_id}.enc

Records

Six types: ID, Medical, Financial, Crypto Wallet, Login, Other.

  • EncryptedTitle
  • EncryptedAll fields (entire JSON blob)
  • EncryptedAttached notes / tags
  • EncryptedEmergency QR payload
  • PlaintextRecord type · timestamps · user ID
records.encrypted_title · records.encrypted_data
Storage

Supabase, with Row Level Security on every table.

We sync your encrypted vault so you can restore on a new phone. We do not — and cannot — read what's inside.

Supabase Storage

Encrypted file blobs uploaded as application/octet-stream. Path is encrypted_documents/{user_id}/{random_id}.enc — random UUID, never the real filename.

Postgres tables

Encrypted vault-key wrappers, encrypted titles, encrypted record JSON, file sizes, timestamps. No plaintext content. No plaintext titles or field values.

Row Level Security

Every table enforces auth.uid() = user_id at the database layer. A stolen API key can't read another user's rows; the database refuses the query.

Delete Account

One DB function wipes documents (Storage), records, recovery data and the auth account server-side in a single transaction. No tombstones, no retention.

In-app security features

Locks on the door, not just the vault.

Encryption protects what's in the vault. These features protect who gets to open it.

Transparency

What we see. What we can't.

The honest version. If anything on the right moved to the left, you'd see it in the changelog before it shipped.

What our servers see
  • Your account email & subscription tier
  • Number of files & total encrypted bytes
  • Random file UUIDs & last-modified timestamps
  • Record type only (ID, Medical, Login, etc.)
  • Your recovery key's SHA-256 hash (verification only)
  • Your encrypted vault key (wrapped, unusable without your password)
What our servers can't see
  • Your password — it's never sent, only used locally
  • Your vault key in plaintext, ever
  • File contents — all application/octet-stream
  • Filenames, MIME types or any record field
  • Record titles (encrypted), notes, tags or attachments
  • Your recovery key itself (only its hash exists)
  • Your biometric data — it stays inside the secure element
Stack

The smallest possible trust footprint.

These are the only platforms involved in your encrypted vault. Nothing else gets to touch it.

Apple Secure Enclave

Hardware-isolated keychain on iPhone and iPad. Holds the vault key behind Face ID / Touch ID — even iOS itself can't read it.

Android Keystore

Hardware-backed keystore (StrongBox where available). Stores the vault key behind fingerprint / device PIN, with the same role as Secure Enclave.

Supabase

Auth, Postgres and Storage. Sees ciphertext only. Row Level Security on every table; one DB function wipes everything on account deletion.

RevenueCat

Subscription state for the Pro tier (entitlement pro). Receives a user ID and entitlement flag — nothing about your documents.

We never sell, share or monetise your data. We built Vaultneur so we can't, even if we wanted to. Your privacy isn't a feature — it's the architecture.

— The Vaultneur team

Questions, or a responsible disclosure?

Email info@vaultneur.com — we read every message. For security issues, please mark the subject line [SECURITY].