# How Vaultwarden Handles Data Encryption at Rest: Server-Side Cryptographic Implementation

> Discover how Vaultwarden encrypts data at rest. Learn about its server-side cryptographic implementation and client-side encryption methods for secure vault storage.

- Repository: [Daniel García/vaultwarden](https://github.com/dani-garcia/vaultwarden)
- Tags: internals
- Published: 2026-03-07

---

**Vaultwarden does not encrypt user vault data on the server—ciphertext arrives pre-encrypted from Bitwarden clients and is stored verbatim, while the server only protects authentication credentials using PBKDF2-HMAC-SHA256 for passwords and Argon2id for admin tokens via the `ring` and `argon2` Rust crates.**

Vaultwarden implements a zero-knowledge architecture where encryption at rest is primarily handled by Bitwarden clients before data ever reaches the server. In this open-source Rust implementation (dani-garcia/vaultwarden), the server stores only ciphertext for vault items, ciphers, and attachments, focusing its cryptographic operations on securing authentication secrets and generating cryptographically secure random tokens.

## The Zero-Knowledge Architecture: Client-Side Encryption

Vaultwarden stores user data—including passwords, secure notes, attachments, and organizational secrets—as opaque encrypted blobs received directly from Bitwarden client applications. The server **never** possesses the plaintext encryption keys for this data, ensuring that even with full database access, vault contents remain inaccessible without the user's master password.

### Master Key (aKey) Storage Model

The per-user master encryption key, referred to as the **aKey**, is stored in the `users` database table as a plain string (`users.akey`), but arrives at the server already encrypted by the client. When a user creates or changes their master password, the Bitwarden client encrypts the aKey using the new password-derived key before transmission. In [`src/db/models/user.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/user.rs), lines 44–48, the server simply persists this encrypted value without modification:

```rust
// src/db/models/user.rs
pub fn set_password(&mut self, new_key: String, new_hash: String, new_iterations: i32) {
    self.akey = new_key;  // Already encrypted by client
    self.password_hash = new_hash;
    self.password_iterations = new_iterations;
}

```

### Database Storage of Encrypted Artifacts

All vault entities follow the same pattern: API keys, send keys, attachment keys, and organization keys arrive as encrypted values or randomly generated keys from the client, and Vaultwarden stores them unchanged. For example, in [`src/db/models/send.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/send.rs), lines 26–31, the Send model stores the encrypted key directly without server-side cryptographic transformation.

## Server-Side Cryptographic Primitives

While Vaultwarden does not encrypt vault data itself, it implements several cryptographic primitives to protect authentication credentials and generate secure tokens.

### PBKDF2-HMAC-SHA256 for Password Verification

Vaultwarden protects user password hashes using **PBKDF2-HMAC-SHA256** with a per-user random salt and configurable iterations (defaulting to **600,000**). The implementation in [`src/crypto.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/crypto.rs) uses the `ring` crate for key derivation:

```rust
// src/crypto.rs
pub fn hash_password(secret: &[u8], salt: &[u8], iterations: u32) -> Vec<u8> {
    let mut out = vec![0u8; OUTPUT_LEN];
    let iterations = NonZeroU32::new(iterations).expect("Iterations can't be zero");
    pbkdf2::derive(DIGEST_ALG, iterations, salt, secret, &mut out);
    out
}

pub fn verify_password_hash(secret: &[u8], salt: &[u8], previous: &[u8], iterations: u32) -> bool {
    let iterations = NonZeroU32::new(iterations).expect("Iterations can't be zero");
    pbkdf2::verify(DIGEST_ALG, iterations, salt, secret, previous).is_ok()
}

```

During authentication, [`src/db/models/user.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/user.rs) (lines 56–62) invokes `hash_password` with the submitted password and stored salt to verify credentials without ever storing the plaintext password.

### Argon2id for Administrative Token Hashing

Administrative tokens and CLI-generated hashes use **Argon2id** (version 0x13) with a memory-hard configuration to resist GPU and ASIC attacks. The default parameters allocate **65,540 KB** of memory with **3 passes** and **4 threads**, implemented in [`src/main.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/main.rs):

```rust
// src/main.rs ── "hash" subcommand
let argon2 = Argon2::new(Argon2id, V0x13, argon2_params.build().unwrap());
let salt = SaltString::encode_b64(&crypto::get_random_bytes::<32>()).unwrap();
let password_hash = argon2.hash_password(password.as_bytes(), &salt)?;
println!("ADMIN_TOKEN='{}'", password_hash);

```

### Cryptographically Secure Random Generation

Vaultwarden generates API keys, email tokens, and file IDs using `ring::rand::SystemRandom` for cryptographically secure random bytes, subsequently encoded via hex or base64. The `generate_api_key` function in [`src/crypto.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/crypto.rs) produces 30-character alphanumeric strings (approximately 178 bits of entropy):

```rust
// src/crypto.rs
pub fn generate_api_key() -> String {
    get_random_string_alphanum(30)
}

pub fn get_random_bytes<const N: usize>() -> [u8; N] {
    let mut bytes = [0u8; N];
    SystemRandom::new().fill(&mut bytes).expect("Failed to generate random bytes");
    bytes
}

```

### Constant-Time Comparison for Timing Attack Prevention

To prevent timing attacks during API key verification and HMAC validation, Vaultwarden uses constant-time equality checks via the `subtle` crate. The `ct_eq` function in [`src/crypto.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/crypto.rs) compares secrets without leaking timing information:

```rust
// src/crypto.rs
pub fn ct_eq<T: AsRef<[u8]>, U: AsRef<[u8]>>(a: T, b: U) -> bool {
    use subtle::ConstantTimeEq;
    a.as_ref().ct_eq(b.as_ref()).into()
}

```

This function protects sensitive comparisons in [`src/api/core/two_factor/email.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/two_factor/email.rs) (line 223) and other authentication flows.

## Critical Source Files for Cryptographic Operations

| File Path | Cryptographic Responsibility |
|-----------|------------------------------|
| **[`src/crypto.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/crypto.rs)** | Core PBKDF2-HMAC-SHA256 password hashing, HMAC-SHA1 signatures, secure random generation, and constant-time comparison |
| **[`src/main.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/main.rs)** | Argon2id implementation for the `hash` CLI command and admin token generation |
| **[`src/db/models/user.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/user.rs)** | Storage and retrieval of encrypted master keys (aKey) and password hash verification |
| **[`src/db/models/send.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/send.rs)** | Example of encrypted blob storage for secure sends |
| **[`Cargo.toml`](https://github.com/dani-garcia/vaultwarden/blob/main/Cargo.toml)** | Dependency specifications for `ring`, `argon2`, `subtle`, `rand`, and `data-encoding` |

## Summary

- Vaultwarden implements **zero-knowledge encryption** where clients encrypt vault data before transmission, leaving the server to store only ciphertext.
- The server protects user credentials using **PBKDF2-HMAC-SHA256** with 600,000 default iterations and per-user salts.
- Administrative secrets use **Argon2id** (v0x13) with 65,540 KB memory hardness for GPU-resistant hashing.
- Cryptographic operations rely on audited Rust crates: **`ring`** for PBKDF2 and random generation, **`argon2`** for admin tokens, and **`subtle`** for constant-time comparisons.
- The master encryption key (aKey) is stored in the database only after client-side encryption with the user's password-derived key.

## Frequently Asked Questions

### Does Vaultwarden encrypt my passwords and vault data on the server?

No. Vaultwarden stores vault data exactly as received from Bitwarden clients—already encrypted using AES-256-CBC or AES-256-GCM. The server never sees the plaintext or the master decryption key, ensuring zero-knowledge architecture.

### What encryption algorithms does Vaultwarden use for authentication?

Vaultwarden uses **PBKDF2-HMAC-SHA256** for deriving and verifying user password hashes with a default of 600,000 iterations, and **Argon2id** (version 0x13) for hashing administrative tokens with memory-hard parameters (65,540 KB, 3 passes, 4 threads).

### Is the master key stored securely in the Vaultwarden database?

The master key (aKey) is stored as a string in the `users` table, but it arrives from the client already encrypted with the user's master password. Vaultwarden never stores the master key in plaintext, nor does it perform server-side encryption of this value.

### Which cryptographic libraries does Vaultwarden depend on?

Vaultwarden uses the **`ring`** crate for PBKDF2, HMAC, and secure random number generation, the **`argon2`** crate for password hashing, the **`subtle`** crate for constant-time equality comparisons, and **`data-encoding`** for hex and base64 operations.