Implementation Status: 🔵 Proposed
Key Management Technical Details
This design document describes the technical implementation of key storage, encryption, and discovery within the Eidetica Users system. For the overall architecture and user-centric key management, see users.md.
Overview
Keys in Eidetica are managed at the user level. Each user owns a set of private keys that are:
- Encrypted with the user's password
- Stored in the user's private database
- Mapped to specific SigKeys in different databases
- Decrypted only during active user sessions
Problem Statement
Key management requires solving several technical challenges:
- Secure Storage: Private keys must be encrypted at rest
- Password-Derived Encryption: Encryption keys derived from user passwords
- SigKey Mapping: Same key can be known by different SigKeys in different databases
- Key Discovery: Finding which key to use for a given database operation
- Memory Security: Clearing sensitive data after use
Technical Components
Password-Derived Key Encryption
Algorithm: Argon2id for key derivation, AES-256-GCM for encryption
Argon2id Parameters:
- Memory cost: 64 MiB minimum
- Time cost: 3 iterations minimum
- Parallelism: 4 threads
- Output: 32 bytes for AES-256
Encryption Process:
- Derive 256-bit encryption key from password using Argon2id
- Generate random 12-byte nonce for AES-GCM
- Serialize private key to bytes
- Encrypt with AES-256-GCM
- Store ciphertext and nonce
Decryption Process:
- Derive encryption key from password (same parameters)
- Decrypt ciphertext using nonce and encryption key
- Deserialize bytes back to SigningKey
Key Storage Format
Keys are stored in the user's private database in the keys subtree as a Table:
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct UserKey {
/// Local key identifier (public key string or hardcoded name)
/// Examples: "ed25519:ABC123..." or "_device_key"
pub key_id: String,
/// Encrypted private key bytes (encrypted with user password-derived key)
pub encrypted_private_key: Vec<u8>,
/// Nonce/IV used for encryption (12 bytes for AES-GCM)
pub nonce: Vec<u8>,
/// Display name for UI/logging
pub display_name: Option<String>,
/// Unix timestamp when key was created
pub created_at: u64,
/// Unix timestamp when key was last used for signing
pub last_used: Option<u64>,
/// Database-specific SigKey mappings
/// Maps: Database ID → SigKey string
pub database_sigkeys: HashMap<ID, String>,
}
Storage Location: User database → keys subtree → Table
Table Key: The key_id field (not stored in struct, used as table key)
SigKey Mapping
A key can be known by different SigKeys in different databases:
Local Key: "ed25519:ABC123..."
├── Database A: SigKey "alice"
├── Database B: SigKey "admin"
└── Database C: SigKey "alice_laptop"
Mapping Storage: The database_sigkeys HashMap in UserKey stores these mappings as database_id → sigkey_string.
Lookup: When creating a transaction, retrieve the appropriate SigKey from the mapping using the database ID.
Database Access Index
To efficiently find which keys can access a database, we build a reverse index from database auth settings:
/// Built by reading _settings.auth from database tips
pub struct DatabaseAccessIndex {
/// Maps: Database ID → Vec<(local_key_id, permission)>
access_map: HashMap<ID, Vec<(String, Permission)>>,
}
Index Building: For each database, read its _settings.auth, match SigKeys to user keys via the database_sigkeys mapping, and store the resulting (key_id, permission) pairs.
Key Lookup: Query the index by database ID to get all user keys with access, optionally filtered by minimum permission level.
Key Discovery
Finding the right key for a database operation involves:
- Get Available Keys: Query the DatabaseAccessIndex for keys with access to the database, filtered by minimum permission if needed
- Filter to Decrypted Keys: Ensure we have the private key decrypted in memory
- Select Best Key: Choose the key with highest permission level for the database
- Retrieve SigKey: Get the mapped SigKey from the
database_sigkeysfield for transaction creation
Memory Security
Decrypted keys are held in memory only during active user sessions:
- Session-Based: Keys decrypted on login, held in memory during session
- Explicit Clearing: On logout, overwrite key bytes with zeros using the
zeroizecrate - Drop Safety: Implement
Dropto automatically clear keys when manager is destroyed - Encryption Key: Also clear the password-derived encryption key from memory
Implementation Details
UserKeyManager Structure
pub struct UserKeyManager {
/// Decrypted private keys (only in memory during session)
/// Map: key_id → SigningKey
decrypted_keys: HashMap<String, SigningKey>,
/// Key metadata (including SigKey mappings)
/// Map: key_id → UserKey
key_metadata: HashMap<String, UserKey>,
/// User's password-derived encryption key
/// Used for encrypting new keys during session
encryption_key: Vec<u8>,
/// Database access index (for key discovery)
access_index: DatabaseAccessIndex,
}
Creation: On user login, derive encryption key from password, decrypt all user's private keys, and build the database access index.
Key Operations:
- Add Key: Encrypt private key with session encryption key, create metadata, store in both maps
- Get Key: Retrieve decrypted key by ID, update last_used timestamp
- Serialize: Export all key metadata (with encrypted keys) for storage
Password Change
When a user changes their password, all keys must be re-encrypted:
- Verify Old Password: Authenticate user with current password
- Derive New Encryption Key: Generate new salt, derive key from new password
- Re-encrypt All Keys: Iterate through decrypted keys, encrypt each with new key
- Update Password Hash: Hash new password with new salt
- Store Updates: Write all updated UserKey records and password hash in transaction
- Update In-Memory State: Replace session encryption key with new one
Security Properties
Encryption Strength
- Key Derivation: Argon2id with 64 MiB memory, 3 iterations
- Encryption: AES-256-GCM (authenticated encryption)
- Key Size: 256-bit encryption keys
- Nonce: Unique 96-bit nonces for each encryption
Attack Resistance
- Brute Force: Argon2id parameters make password cracking expensive
- Replay Attacks: Nonces prevent reuse of ciphertexts
- Tampering: GCM authentication tag detects modifications
- Memory Dumps: Keys cleared from memory on logout
Limitations
- Password Strength: Security depends on user password strength
- No HSM Support: Keys stored in software (future enhancement)
- No Key Recovery: Lost password means lost keys (by design)
Performance Considerations
Login Performance
Password derivation is intentionally slow:
- Argon2id: ~100-200ms per derivation
- Key decryption: ~1ms per key
- Total login time: ~200ms + (num_keys × 1ms)
This is acceptable for login operations.
Runtime Performance
During active session:
- Key lookups: O(1) from HashMap
- SigKey lookups: O(1) from HashMap
- Database key discovery: O(n) where n = number of keys
- No decryption overhead (keys already decrypted)
Testing Strategy
-
Unit Tests:
- Password derivation consistency
- Encryption/decryption round-trips
- Key serialization/deserialization
- SigKey mapping operations
-
Security Tests:
- Verify different passwords produce different encrypted keys
- Verify wrong password fails decryption
- Verify nonce uniqueness
- Verify memory clearing
-
Integration Tests:
- Full user session lifecycle
- Key addition and usage
- Password change flow
- Multiple keys with different SigKey mappings
Future Enhancements
- Hardware Security Module Support: Store keys in HSMs
- Key Derivation Tunning: Adjust Argon2 parameters based on hardware
- Key Backup/Recovery: Secure key recovery mechanisms
- Multi-Device Sync: Sync encrypted keys across devices
- Biometric Authentication: Use biometrics instead of passwords where available
Conclusion
This key management implementation provides:
- Strong encryption of private keys at rest
- User-controlled key ownership through passwords
- Flexible SigKey mapping for multi-database use
- Efficient key discovery for database operations
- Memory security through session-based decryption
For the overall architecture and user management, see the Users design.