How RabbitKey Encrypts Your Vault

RabbitKey derives a key from your master password and uses it to seal every vault entry before anything is written to disk. This article follows that path end to end: from the password you type to the ciphertext stored on your device.

From a Password to a Key

A master password is a human-memorable string. Encryption requires a fixed-length, high-entropy key. The gap between these two things is bridged by a Key Derivation Function (KDF).

RabbitKey uses PBKDF2-HMAC-SHA256 with the following parameters:

  • 100,000 iterations — each unlock requires 100,000 rounds of HMAC-SHA256, so every guess in an offline brute-force attack carries that same cost
  • 32-byte (256-bit) random salt — unique per vault, stored alongside the encrypted data, prevents precomputed rainbow-table attacks
  • 256-bit output key — the derived key is exactly the size XChaCha20-Poly1305 requires

The salt is not secret. Its purpose is uniqueness: two users with the same password produce completely different keys because their salts differ.

About the iteration count

OWASP's password-storage guidance has historically put 100,000 in range for PBKDF2-HMAC-SHA256, while NIST's more recent guidance recommends 600,000. RabbitKey's current count is 100,000 — below that newer recommendation. It raises the cost of an offline attack against a strong master password, but it is not a substitute for choosing a strong password in the first place. The account record stores the KDF identifier and its parameters, so the iteration count can be raised in a future version and existing vaults re-wrapped on unlock. (PBKDF2 is the only KDF implemented today; do not assume a memory-hard function such as Argon2 is in use.)

Two Keys: Wrapping the Master Key

The key derived from your password does not directly encrypt your entries. RabbitKey uses an envelope (key-wrapping) design:

  1. Each vault has a single random master key — 32 bytes, generated once when the vault is created.
  2. The PBKDF2-derived key encrypts ("wraps") that master key. The wrapped master key is what gets stored.
  3. On unlock, the derived key decrypts the master key, and the master key is what actually encrypts and decrypts your entries.

This indirection has a concrete payoff: changing your master password does not re-encrypt your whole vault. Only the small wrapped-master-key blob is re-wrapped with the new password-derived key. It is also why your Recovery Kit — which is the master key itself, encoded — keeps working after a password change: the underlying master key never changed.

The Cipher: XChaCha20-Poly1305

Both the wrapping step and the entries themselves use XChaCha20-Poly1305, an Authenticated Encryption with Associated Data (AEAD) construction.

The key parameters:

Parameter Value
Key size 256 bits
Nonce size 192 bits (24 bytes)
Authentication tag Poly1305 MAC (128 bits)

A fresh, random 24-byte nonce is generated for every encryption operation. The nonce is stored alongside the ciphertext — it is not secret, but it must never repeat for the same key. XChaCha20's extended 192-bit nonce makes collisions negligibly probable even across billions of operations.

What AEAD Means for You

AEAD provides two guarantees simultaneously:

  • Confidentiality — without the key, the ciphertext reveals nothing about the plaintext
  • Integrity and authenticity — the Poly1305 MAC authenticates the message. If any byte is modified after encryption (whether by storage corruption or an attacker), decryption fails loudly with an authentication error rather than returning garbage data

This means RabbitKey doesn't just hide your passwords — it detects tampering. A corrupted or modified vault file will be rejected rather than silently decrypted into wrong data.

The Full Path

User types master password │ ▼ PBKDF2-HMAC-SHA256 iterations: 100,000 salt: 32-byte random (stored) output: 256-bit password-derived key │ ▼ Unwrap the master key derived key decrypts the stored wrapped master key → 32-byte vault master key (held in memory only) │ ▼ XChaCha20-Poly1305 encryption of entries key: 32-byte master key nonce: 24-byte random (per operation, stored) output: ciphertext + Poly1305 MAC │ ▼ Stored on device (encrypted vault)

The master password and the derived key are held only in memory during a session — neither is written to disk. What persists on disk is the salt, the wrapped master key, and the ciphertext.

Why XChaCha20-Poly1305 Instead of AES-GCM?

Both are secure AEAD ciphers. XChaCha20-Poly1305 has two practical advantages on mobile:

  1. Consistent in software — AES-GCM is fast and safe with AES-NI hardware acceleration, but in pure-software implementations without it, AES is more prone to timing side-channels. XChaCha20 is designed to run fast and in constant time in software, which matters on older or lower-end hardware.
  2. Larger nonce — the 192-bit nonce (vs AES-GCM's 96-bit) makes random nonce generation safer at scale, reducing nonce-collision probability.

What This Means in Practice

If an attacker obtains your encrypted vault — through device theft, cloud storage compromise, or any other means — they get the salt, the wrapped master key, and the ciphertext. To read anything, they must recover the master key, which means decrypting the wrapped master key, which means deriving the password-derived key through PBKDF2. That puts them back at guessing your master password, paying the 100,000-iteration cost on every attempt. There is no shortcut that skips the password.

All of this happens on your device, before any sync. Your cloud provider receives ciphertext only — see How Zero-Knowledge Sync Works. For why this is an architectural property rather than a promise, see Local-First Security Architecture & Threat Model. And for what happens if you lose your master password, see Your Recovery Kit, Explained.