Overview
Bitwarden implements a comprehensive cryptographic architecture built on end-to-end encryption principles. All sensitive data is encrypted client-side before transmission to servers, ensuring that only the user can decrypt their vault data.
Core Cryptographic Principles
End-to-End Encryption
Bitwarden employs end-to-end encryption where:
- Client-side encryption: All encryption and decryption operations occur on the client
- Zero-knowledge architecture: The server never has access to unencrypted data or encryption keys
- User-controlled keys: Only the user possesses the keys needed to decrypt their vault
The server cannot decrypt user data. If a user loses their master password, their encrypted vault data cannot be recovered by Bitwarden.
Key Hierarchy
Bitwarden uses a hierarchical key structure:
Master Key
The master key is derived from the user’s master password using a Key Derivation Function (KDF):
// From libs/key-management/src/key.service.ts:254
async makeMasterKey(password: string, email: string, kdfConfig: KdfConfig): Promise<MasterKey> {
email = email.trim().toLowerCase();
const masterKey = await this.keyGenerationService.deriveKeyFromPassword(
password,
email,
kdfConfig,
) as MasterKey;
return masterKey;
}
- Input: Master password + email (used as salt)
- Process: KDF (PBKDF2 or Argon2id)
- Output: 256-bit master key
- Storage: Never stored; derived on-demand from password
User Key
The user key is a symmetric key that actually encrypts vault data:
// From libs/key-management/src/key.service.ts:187
async makeUserKey(masterKey: MasterKey): Promise<[UserKey, EncString]> {
const newUserKey = await this.keyGenerationService.createKey(512);
return this.buildProtectedSymmetricKey(masterKey, newUserKey);
}
- Type: 512-bit symmetric key (AES-256 + HMAC-SHA256)
- Generation: Cryptographically secure random number generator (CSPRNG)
- Protection: Encrypted with the master key
- Storage: Encrypted user key stored server-side and locally
Organization Keys
For shared organization vaults:
// From libs/key-management/src/abstractions/key.service.ts:231
abstract makeOrgKey<T extends OrgKey | ProviderKey>(userId: UserId): Promise<[EncString, T]>;
- Generation: 512-bit symmetric key per organization
- Protection: Encrypted with user’s public key (RSA-2048)
- Sharing: Encrypted separately for each organization member
Encryption Types
Bitwarden supports multiple encryption algorithms:
Symmetric Encryption
// From libs/common/src/platform/enums/encryption-type.enum.ts
export enum EncryptionType {
AesCbc256_B64 = 0, // Legacy: AES-256-CBC
AesCbc256_HmacSha256_B64 = 2, // Current: AES-256-CBC + HMAC-SHA256
CoseEncrypt0 = 7, // Modern: XChaCha20-Poly1305
}
AES-256-CBC with HMAC-SHA256 (Primary)
- Encryption: AES-256 in CBC mode
- Authentication: HMAC-SHA256
- Key size: 512 bits (256-bit encryption key + 256-bit MAC key)
- IV: Randomly generated per encryption operation
// From libs/common/src/platform/models/domain/symmetric-crypto-key.ts:49
if (key.byteLength === 64) {
this.innerKey = {
type: EncryptionType.AesCbc256_HmacSha256_B64,
encryptionKey: key.slice(0, 32),
authenticationKey: key.slice(32),
};
}
All encrypted data must be authenticated with HMAC-SHA256 to prevent tampering and chosen-ciphertext attacks.
XChaCha20-Poly1305 (Modern)
- Algorithm: XChaCha20-Poly1305 AEAD
- Encoding: COSE (CBOR Object Signing and Encryption)
- Benefits: Authenticated encryption, better performance on some platforms
Asymmetric Encryption
export enum EncryptionType {
Rsa2048_OaepSha256_B64 = 3, // RSA-OAEP with SHA-256
Rsa2048_OaepSha1_B64 = 4, // Legacy: RSA-OAEP with SHA-1
Rsa2048_OaepSha256_HmacSha256_B64 = 5, // RSA-OAEP + HMAC
Rsa2048_OaepSha1_HmacSha256_B64 = 6, // Legacy: RSA-OAEP + HMAC
}
- Key size: 2048-bit RSA
- Padding: OAEP (Optimal Asymmetric Encryption Padding)
- Usage: Organization key sharing, key encapsulation
Key Derivation Functions
Bitwarden supports two KDFs for deriving the master key from the master password:
PBKDF2-SHA256
// From libs/key-management/src/models/kdf-config.ts:17
export class PBKDF2KdfConfig {
static ITERATIONS = new RangeWithDefault(600_000, 2_000_000, 600_000);
kdfType: KdfType.PBKDF2_SHA256 = KdfType.PBKDF2_SHA256;
iterations: number;
}
- Default iterations: 600,000 (OWASP recommended minimum)
- Range: 600,000 to 2,000,000 iterations
- Algorithm: PBKDF2 with SHA-256
- Purpose: Derive master key from password + email
Using fewer than 600,000 iterations significantly weakens password security. Always use the default or higher values.
Argon2id
// From libs/key-management/src/models/kdf-config.ts:66
export class Argon2KdfConfig {
static MEMORY = new RangeWithDefault(16, 1024, 64); // MB
static PARALLELISM = new RangeWithDefault(1, 16, 4); // threads
static ITERATIONS = new RangeWithDefault(2, 10, 3); // iterations
}
- Default: 3 iterations, 64 MB memory, 4 parallel threads
- Algorithm: Argon2id (winner of Password Hashing Competition)
- Benefits: Memory-hard, resistant to GPU/ASIC attacks
Encryption Services
The encryption architecture is modular:
EncryptService
// From libs/common/src/key-management/crypto/abstractions/encrypt.service.ts
abstract class EncryptService {
abstract encryptString(plainValue: string, key: SymmetricCryptoKey): Promise<EncString>;
abstract decryptString(encString: EncString, key: SymmetricCryptoKey): Promise<string>;
abstract wrapSymmetricKey(keyToBeWrapped: SymmetricCryptoKey, wrappingKey: SymmetricCryptoKey): Promise<EncString>;
abstract unwrapSymmetricKey(keyToBeUnwrapped: EncString, wrappingKey: SymmetricCryptoKey): Promise<SymmetricCryptoKey>;
}
Provides high-level encryption/decryption operations:
- String encryption/decryption
- File encryption/decryption
- Key wrapping/unwrapping
- Key encapsulation (RSA)
CryptoFunctionService
// From libs/common/src/key-management/crypto/abstractions/crypto-function.service.ts
abstract class CryptoFunctionService {
abstract pbkdf2(password: string | Uint8Array, salt: string | Uint8Array, algorithm: "sha256" | "sha512", iterations: number): Promise<Uint8Array>;
abstract aesGenerateKey(bitLength: 128 | 192 | 256 | 512): Promise<CsprngArray>;
abstract randomBytes(length: number): Promise<CsprngArray>;
abstract rsaGenerateKeyPair(length: 2048): Promise<[Uint8Array, Uint8Array]>;
}
Provides low-level cryptographic primitives:
- PBKDF2/Argon2id key derivation
- HKDF key expansion
- Hash functions (SHA-1, SHA-256, SHA-512)
- HMAC operations
- RSA operations
- Random number generation (CSPRNG)
Encrypted data is serialized in a specific format:
// Format: <encType>.<iv>|<data>|<mac>
// Example: 2.base64iv|base64data|base64mac
class EncString {
encryptionType: EncryptionType;
iv: string; // Base64-encoded initialization vector
data: string; // Base64-encoded ciphertext
mac: string; // Base64-encoded HMAC
}
- AES-CBC-256 + HMAC:
2.iv|data|mac
- RSA-OAEP:
3.data
- RSA-OAEP + HMAC:
5.data|mac
Security Best Practices
Never store master keys or unencrypted user keys persistently. Keys should only exist in memory during active sessions.
Key Storage Guidelines
- Master Key: Never stored; always derived from password
- User Key: Stored encrypted with master key
- Organization Keys: Stored encrypted with user’s public key
- Private Key: Stored encrypted with user key
Encryption Guidelines
- Always use authenticated encryption (HMAC or AEAD)
- Generate unique IVs for each encryption operation
- Use CSPRNG for all random data (keys, IVs, salts)
- Validate MACs before decrypting (prevent oracle attacks)
References
libs/key-management/ - Key management implementation
libs/common/src/key-management/crypto/ - Cryptographic services
libs/common/src/platform/enums/encryption-type.enum.ts - Encryption types