docs/core/key-manager.mdx
The KeyManager is Spacedrive's unified cryptographic secret storage system. It provides encrypted storage for all sensitive data including device keys, library encryption keys, paired device session keys, and cloud credentials.
KeyManager uses redb, an embedded key-value database, for encrypted secret storage:
<data_dir>/secrets.redbAll secrets are encrypted at rest using the device key. The database provides ACID guarantees and transactional operations.
Device Key (256-bit, from OS keychain)
│
├─ Library Keys (per-library encryption)
│ └─ Used by: Cloud credentials, library-specific secrets
│
├─ Paired Device Data (session keys, device info)
│ └─ Used by: P2P networking, device pairing
│
└─ Arbitrary Secrets (application-level secrets)
└─ Used by: Extensions, custom storage needs
Device Key:
Spacedrive service)<data_dir>/device_key (development and testing only)Encrypted Secrets:
<data_dir>/secrets.redbEach library has a unique encryption key used for encrypting library-specific secrets:
// Automatically generated when first accessed
let library_key = key_manager.get_library_key(library_id).await?;
Key format: library_{uuid}
Used for:
Device pairing information and session keys for P2P communication:
pub struct PersistedPairedDevice {
device_info: DeviceInfo,
session_keys: SessionKeys,
paired_at: DateTime<Utc>,
last_connected_at: Option<DateTime<Utc>>,
trust_level: TrustLevel,
relay_url: Option<String>,
}
Key format: paired_device_{uuid}
Includes:
OAuth tokens and API keys for cloud storage integrations:
pub struct CloudCredential {
volume_fingerprint: String,
encrypted_credential: Vec<u8>, // Encrypted with library key
service_type: String, // "google_drive", "dropbox", etc.
}
Note: Cloud credentials are stored in the library database, but encrypted using library keys from KeyManager.
General-purpose encrypted storage for application or extension needs:
// Store any secret
key_manager.set_secret("my_api_key", b"secret_value").await?;
// Retrieve
let secret = key_manager.get_secret("my_api_key").await?;
// Delete
key_manager.delete_secret("my_api_key").await?;
Algorithm: XChaCha20-Poly1305
Process:
[nonce(24) | ciphertext | tag(16)]The device key never exists in plaintext on disk (except in development with file fallback):
Production:
User's device
└─ OS Keychain (hardware-backed on supported devices)
└─ Device Key (256-bit)
└─ Protected by OS authentication (biometrics, password)
Development and Testing Only:
<data_dir>/device_key (file fallback)
└─ Plaintext 32-byte file
└─ Protected only by OS file permissions
└─ Not recommended for production
Library keys are automatically generated and do not require manual rotation.
The KeyManager is initialized once at application startup and shared via Arc:
// In Core initialization
let device_key_fallback = data_dir.join("device_key");
let key_manager = Arc::new(KeyManager::new_with_fallback(
data_dir.clone(),
Some(device_key_fallback),
)?);
// Get device key
let device_key = key_manager.get_device_key().await?;
// Get library key (auto-generates if not exists)
let library_key = key_manager.get_library_key(library_id).await?;
// Store a secret
key_manager.set_secret("api_token", token_bytes).await?;
// Retrieve a secret
let token = key_manager.get_secret("api_token").await?;
// Delete a secret
key_manager.delete_secret("api_token").await?;
// Close database (on shutdown)
key_manager.close().await?;
Device Manager (network identity):
// DeviceManager uses KeyManager for device key
let device = DeviceManager::init(&data_dir, key_manager.clone(), None)?;
let device_key = device.master_key().await?;
let identity = NetworkIdentity::from_device_key(&device_key).await?;
Device Pairing (session keys):
// Store paired device
let persistence = DevicePersistence::new(key_manager.clone());
persistence.add_paired_device(
device_id,
device_info,
session_keys,
relay_url,
).await?;
// Load paired devices
let devices = persistence.load_paired_devices().await?;
Cloud Credentials:
// Encrypt and store cloud credential
let cloud_manager = CloudCredentialManager::new(library_id, key_manager.clone());
cloud_manager.store_credential(
volume_fingerprint,
credential,
service_type,
).await?;
use sd_core::crypto::key_manager::KeyManagerError;
match key_manager.get_secret("my_key").await {
Ok(secret) => println!("Secret: {:?}", secret),
Err(KeyManagerError::KeyNotFound(key)) => {
println!("Secret '{}' not found", key);
}
Err(KeyManagerError::EncryptionError(e)) => {
eprintln!("Encryption failed: {}", e);
}
Err(e) => eprintln!("KeyManager error: {}", e),
}
The device key is cached in memory after first retrieval to avoid repeated OS keychain access:
// First call: retrieves from keychain
let key1 = key_manager.get_device_key().await?;
// Subsequent calls: returns cached value (fast)
let key2 = key_manager.get_device_key().await?;
KeyManager uses async locking for safe concurrent access:
// Safe to call from multiple tasks
let key_manager = Arc::new(KeyManager::new(data_dir)?);
tokio::spawn({
let km = key_manager.clone();
async move { km.set_secret("key1", b"value").await }
});
tokio::spawn({
let km = key_manager.clone();
async move { km.get_secret("key2").await }
});
The redb database grows with the number of secrets stored. Typical sizes:
The database is compacted automatically by redb.
These systems have been consolidated into KeyManager for better security and reliability. </Info>
If KeyManager can't access the OS keychain:
gnome-keyring or kwallet<data_dir>/device_keyIf secrets.redb becomes corrupted:
# Backup first
cp ~/.spacedrive/secrets.redb ~/.spacedrive/secrets.redb.backup
# Remove corrupted database (will lose paired devices!)
rm ~/.spacedrive/secrets.redb
# Restart Spacedrive (new database will be created)
// List all paired device keys
let db = key_manager.db.read().await;
let read_txn = db.begin_read()?;
let table = read_txn.open_table(SECRETS_TABLE)?;
for item in table.iter()? {
let (key, _value) = item?;
if key.value().starts_with("paired_device_") {
println!("Device key: {}", key.value());
}
}
Ensure strict permissions on sensitive files:
# Check permissions
ls -la ~/.spacedrive/secrets.redb
ls -la ~/.spacedrive/device_key # If using fallback
# Fix if needed
chmod 600 ~/.spacedrive/secrets.redb
chmod 600 ~/.spacedrive/device_key
The device key is critical for accessing all secrets:
# macOS: Export device key from Keychain
security find-generic-password -s "Spacedrive" -a "device_key" -w
# Linux: Backup entire secrets database
cp ~/.spacedrive/secrets.redb ~/backup/
# Windows: Export from Credential Manager
cmdkey /list | findstr Spacedrive