Security Model
Overview
Beaver Notes has two independent encryption layers:
- App-level encryption: Automatic AES-256-GCM encryption of all note content and assets at rest
- Per-note password lock: Manual password protection for individual notes
Both are implemented in Rust (src-tauri) with the frontend delegating via Tauri IPC. Heavy crypto operations run in a Web Worker pool (up to 4 workers) to avoid blocking the UI thread.
App-Level Encryption
Key Hierarchy
User passphrase
→ Argon2id (or PBKDF2) → KEK (Key Encryption Key)
→ wraps random 256-bit Data Key
→ AES-256-GCM encrypts note content
Manifest File
Stored at {app_data_dir}/app-crypto/manifest.v3.json:
{
"version": 3,
"scope": "app",
"argon2_salt_hex": "...",
"argon2_memory_kib": 16384,
"argon2_iterations": 2,
"argon2_parallelism": 2,
"password_check": {
"nonce": "...",
"cipher": "..."
},
"wrapped_key": {
"nonce": "...",
"cipher": "..."
}
}
Key Derivation
| Algorithm | Parameters | Used for |
|---|---|---|
| Argon2id (v3 manifests) | 16 MiB memory, 2 iterations, 2 parallelism | New encryption setups |
| PBKDF2-HMAC-SHA256 (legacy) | 100,000 iterations | Pre-v3 manifests |
The derived KEK encrypts a randomly generated 256-bit data key. The data key is stored in the manifest wrapped with AES-256-GCM.
Note Encryption
Each note’s content JSON is serialized, encrypted with AES-256-GCM using the data key, and stored as:
{
"ae": 2,
"nonce": "<96-bit base64>",
"cipher": "<ciphertext base64>"
}
Asset Encryption
Assets (images, files) are encrypted with streaming AES-256-GCM:
- Format:
BNA3magic header (4 bytes) + nonce seed + variable-length encrypted chunks - Chunk size: 256 KiB
- Per-chunk nonce: Derived from HMAC with the seed
- On disk: Encrypted assets get a
.encextension
Password Verification
The manifest includes a password_check field containing a well-known string ("BeaverNotes-app-manifest-v3") encrypted with the data key. On unlock, the app decrypts this field: if it matches, the passphrase is correct.
Per-Note Password Lock
Separate from app-level encryption:
- Each locked note’s content is encrypted with a key derived from a per-user password
- The password hash (bcrypt) is stored in the OS keychain via the
keyringcrate - The
passwordStore(src/store/passwd.js) manages bcrypt verification and session caching - The shared key is kept in memory for the session and cleared on app close
Platform Safe Storage
The secure_blob.rs module provides an encrypted blob cache:
- Primary: OS keychain via the
keyringcrate (macOS Keychain, Linux Secret Service, Windows Credential Manager) - Fallback: File-based key at
~/.local/share/com.beaver-notes.beaver-notes/master.key(encrypted with a device-specific key)
Sync Encryption
When app-level encryption is enabled, sync data is also encrypted:
- Sync manifests use scope
"sync"with check string"BeaverNotes-sync-manifest-v3" - Commit payloads are encrypted via
encryption_encrypt_sync_payload/encryption_decrypt_sync_payload - Sync encryption keys are stored in the sync directory under
crypto/
Threat Model
| Threat | Mitigation |
|---|---|
| Physical device access | App-level encryption at rest. Without passphrase, note content is inaccessible |
| Cloud storage provider access to sync folder | Sync commits are encrypted with the app key |
| Keylogger/malware | Passphrase is in memory only during unlock. Data key is cached in Rust memory |
| OS keychain compromise | Master key is encrypted with device-specific key as fallback |
| Brute force passphrase | Argon2id (memory-hard, 16 MiB) makes GPU/ASIC attacks expensive |
Key Files
| File | Purpose |
|---|---|
src-tauri/src/shared/crypto.rs |
Core crypto: AES-256-GCM, Argon2id, PBKDF2, key derivation, asset streaming encryption |
src-tauri/src/secure_blob.rs |
OS keychain integration with encrypted file fallback |
src/utils/crypto/codec.js |
Frontend crypto: Web Worker pool, PBKDF2, AES-GCM operations |
src/utils/crypto/worker.js |
Web Worker script for off-thread crypto |
src/utils/crypto/encryption.js |
App-level encryption lifecycle (enable, disable, migrate) |
src/utils/crypto/noteCrypto.js |
Legacy per-note encryption |
src/store/passwd.js |
Password hashing, verification, session management |
src/store/note/lock.js |
Per-note locking/unlocking |
src/store/note/encryption.js |
Bulk encrypt/decrypt notes during migration |