Bitvaulter Security Whitepaper

DRAFT - pending independent third-party security review and penetration test before publication. Describes the design as implemented; not a substitute for an external audit.

Last updated: 2026-06-27 (date to be confirmed at publication).


1. Introduction

Bitvaulter (bitvaulter.com) is a zero-knowledge, end-to-end-encrypted password and secrets manager built on a model compatible with Bitwarden. It serves a dual audience: consumers and families who want a private place to store credentials, and developers and teams who need to manage shared secrets and service-account tokens.

Bitvaulter is self-hostable today, and a hosted option is planned. This whitepaper is written for technically literate readers and security reviewers. It describes the cryptographic design, the trust boundaries, what the server can and cannot see, and the limitations we currently know about. We aim to be accurate and to avoid overstating any guarantee.

The central design principle is straightforward: the server stores only ciphertext and the metadata needed to route it. The keys that unlock that ciphertext never leave the user's device in a usable form. Where that principle has limits, this document states them plainly.


2. Overview and threat model

Encryption is only meaningful relative to a stated adversary. This section describes what Bitvaulter is designed to defend against, and - just as importantly - what it does not defend against.

2.1 What we defend against

Server compromise. If an attacker gains control of the Bitvaulter backend, they obtain only encrypted vault data and the metadata required to serve it. They do not obtain the master password, the Master Key, the User Key, any item key, or any plaintext vault content. The server never holds the material needed to decrypt a user's vault (see Section 7).

Database theft. If an attacker exfiltrates the entire database, the result is the same as server compromise with respect to confidentiality: the attacker holds wrapped keys and ciphertext, not plaintext. The protection of that ciphertext reduces to the strength of the user's master password and the key-derivation function applied to it (see Sections 4 and 12).

Network attacker. All client-server traffic is carried over HTTPS/TLS, with HSTS to resist downgrade and stripping. A passive or active network attacker sees only encrypted transport carrying already-encrypted payloads (see Section 11).

2.2 What we do NOT defend against

We are explicit about the limits of the model. Bitvaulter does not protect against:


3. Architecture and backend

Bitvaulter's backend runs on Mortarbase, a Supabase-compatible platform. It provides three relevant components:

All cryptographic operations that touch plaintext or key material occur on the client. The backend's role is to authenticate requests, enforce authorization, and store and serve opaque ciphertext.


4. Key hierarchy

Bitvaulter derives all encryption keys from the user's master password through a layered hierarchy. Each layer exists so that the expensive, password-bound derivation happens once, while day-to-day operations use fast symmetric keys, and so that keys can be rewrapped or rotated without re-deriving from the password.

master password
      |
      |  PBKDF2-HMAC-SHA256, 600,000 iterations
      |  salt = per-account random value (via prelogin)
      v
  Master Key (256-bit)
      |
      |  HKDF-Expand(info = "enc" / "mac")
      v
  Stretched key (512-bit) = 256-bit AES key || 256-bit MAC key
      |
      |  unwraps
      v
  User Key (random 512-bit), stored wrapped under the stretched key
      |
      |  unwraps
      v
  Per-item keys (random, one per vault item), each stored wrapped under the User Key

4.1 Master Key derivation (KDF)

The Master Key is derived with PBKDF2-HMAC-SHA256 at 600,000 iterations, using a per-account random salt. The client fetches this salt before authenticating, via a prelogin lookup keyed by email. This produces a 256-bit Master Key. Accounts created before random salts were introduced may still use the lowercased/trimmed email address as the salt, for backward compatibility.

The KDF parameters (type and iteration count) are stored per account, so they can be upgraded over time. Argon2id is a planned, negotiable future KDF (see Section 12).

4.2 Stretched key

The 256-bit Master Key is expanded with HKDF-Expand into a 512-bit stretched key, using distinct info strings ("enc" and "mac"). The result is split into a 256-bit AES key and a 256-bit MAC key.

4.3 User Key

A random 512-bit User Key is generated for the account and stored wrapped (encrypted) under the stretched key. The User Key - not the Master Key - is what protects the user's vault items. Because the User Key is independent of the password-derived stretched key, the design allows the wrapping to change (for example, on a password change) without re-encrypting every item.

4.4 Per-item keys

Each vault item gets its own random item key, stored wrapped under the User Key. Item content is encrypted with its own item key. This per-item key design limits the blast radius of any single key and is what allows individual items to be rewrapped for sharing (see Section 9).


5. EncString formats

Bitvaulter serializes encrypted values using a tagged "EncString" wire format. Two types are relevant.

5.1 Type 2 - symmetric (AES-256-CBC + HMAC-SHA256)

Type 2 is the workhorse symmetric format. It uses AES-256-CBC for confidentiality and HMAC-SHA256 for integrity, in an encrypt-then-MAC construction.

Wire format:

2.<iv-b64>|<ciphertext-b64>|<mac-b64>

The MAC covers iv || ciphertext. On decryption, the MAC is verified with a constant-time comparison before any decryption is attempted. This ordering (verify integrity first, in constant time) is what makes the construction resistant to padding-oracle and timing attacks against the CBC layer.

5.2 Type 4 - asymmetric (RSA-2048 OAEP-SHA1)

Type 4 uses RSA-2048 with OAEP (SHA-1) and is used to wrap keys to a recipient's public key for sharing. This is how a symmetric key (such as a collection key) is delivered to another member: it is encrypted to that member's RSA public key so that only the holder of the corresponding private key can unwrap it.

OAEP with SHA-1 is retained for Bitwarden wire compatibility. We treat this as a known compatibility trade-off rather than a forward-looking recommendation; see Section 12.


6. File and attachment encryption

Encrypted files and attachments use a self-contained binary blob with the following layout:

iv (16 bytes) || ciphertext || mac (32 bytes)

The 16-byte IV and 32-byte (HMAC-SHA256) MAC are stored alongside the ciphertext in a single blob, so an encrypted file is self-describing and can be stored as an opaque object. The encrypted objects live in a private, owner-scoped Storage bucket (see Sections 7 and 8).


7. What the server stores vs. never receives

7.1 What the server stores (ciphertext and metadata only)

The backend persists the following:

Every row and every object is scoped by Row-Level Security to the authenticated user (see Section 8).

7.2 What the server never receives

The server never receives:

As stated in Section 2, there is no recovery path: if a user loses their master password, neither the operator nor anyone else can decrypt the vault.


8. Authorization with Row-Level Security

Authorization is enforced in the database itself using PostgreSQL Row-Level Security. Every item row and every stored file object is scoped to the authenticated user via auth.uid(), so a user can only read or write their own rows and objects.

Enforcing access control at the row level - rather than relying solely on application code - means that even a flaw in an upper layer does not by itself grant a user access to another user's encrypted data. Note that RLS governs access to ciphertext; it is not a confidentiality control over plaintext, which is protected by the key hierarchy described above.


9. Authentication model

Bitvaulter separates the key derived from the master password from the credential used to authenticate to the server.

The value sent to the server is the Master Password Hash, defined as:

Master Password Hash = base64( PBKDF2(Master Key, master password, 1 iteration) )

This hash - not the master password, and not the Master Key - is what is transmitted to the server during authentication. GoTrue then stores only a bcrypt of the Master Password Hash.

The result is two independent barriers:

Because the server only ever sees the Master Password Hash and stores only a bcrypt of it, the master password itself never reaches the server in any form.


10. Sharing and organizations

10.1 Collections

Items can be grouped into collections for sharing. When an item is shared via a collection, it is rewrapped under a collection key. The collection key is then RSA-wrapped to each member's public key (EncString type 4, Section 5.2), so each member can unwrap the collection key with their own private key and thereby decrypt the collection's items.

This keeps the zero-knowledge property intact for sharing: the server stores the wrapped collection keys but never holds the unwrapped collection key or the recipients' private keys.

10.2 Forward-secure rotation on member removal

When a member is removed from a collection, Bitvaulter triggers a forward-secure rotation of the affected collection keys. A new collection key is established and distributed to the remaining members, so that the removed member's old key cannot decrypt data created after their removal.

We are precise about the scope of this guarantee: rotation protects future data. A former member who retained copies of previously accessible plaintext, or who captured the old key material while still a member, is outside what rotation can undo.

10.3 Machine and service-account tokens

For automated workloads, Bitvaulter issues machine/service-account tokens that separate authentication from decryption:

Because the authentication path stores only a one-way hash, and the decryption key is derived along a separate path from the raw secret, the server can authenticate a token without being able to decrypt with it. A server compromise therefore does not yield the decryption capability associated with these tokens.


11. Breach (pwned-password) check

Bitvaulter can check whether a password appears in known breach corpora using the Have I Been Pwned (HIBP) k-anonymity range API. Only the first 5 hexadecimal characters of the password's SHA-1 hash are sent to the range API; the full hash and the password itself never leave the device. The client receives the matching range and completes the comparison locally.


12. Transport and web client hardening


13. Encrypted local backup

Bitvaulter supports an encrypted local backup export. The export is itself encrypted under a separate export password, independent of the master password. This keeps an exported backup confidential at rest while ensuring that compromise of an export does not directly reveal the account's master password or its derived keys.


14. Data retention and deletion


15. Known limitations and roadmap

We believe an honest accounting of current limitations is part of a credible security posture.

This section will be updated as items are completed or as the design changes.


16. Reporting a vulnerability

We welcome reports from the security community. Please refer to our security.txt for the current, authoritative reporting contact and policy:

https://bitvaulter.com/.well-known/security.txt

Contact address:


Appendix A: Cryptographic parameters at a glance

Purpose Mechanism
Master Key derivation (KDF) PBKDF2-HMAC-SHA256, 600,000 iterations, salt = per-account random value (via prelogin; legacy accounts may use email), 256-bit output
Key stretching HKDF-Expand (info "enc" / "mac") -> 512-bit (256-bit AES key || 256-bit MAC key)
User Key Random 512-bit, wrapped under the stretched key
Per-item keys Random per item, wrapped under the User Key
Symmetric encryption (EncString type 2) AES-256-CBC + HMAC-SHA256, encrypt-then-MAC, constant-time MAC verify before decrypt
Asymmetric key wrap (EncString type 4) RSA-2048 OAEP (SHA-1)
File/attachment blob iv(16) || ciphertext || mac(32)
Authentication credential Master Password Hash = base64(PBKDF2(Master Key, master password, 1 iteration)); GoTrue stores bcrypt of it
Service-token AUTH One-way hash of token secret stored server-side (PBKDF2-HMAC-SHA256 today; Argon2id planned)
Service-token DECRYPT HKDF of raw secret with a distinct info string
Breach check HIBP k-anonymity range API; first 5 SHA-1 hex chars sent only

DRAFT - for review by qualified legal counsel before publication. This is not legal advice.

Placeholders requiring owner/counsel confirmation appear in this document as bracketed items.