Encryption

MemDb supports AES-256-CBC encryption at the record level. Encrypted and unencrypted records can coexist in the same dataset — configuring a key enables per-record opt-in encryption at insert time, not whole-database encryption. A fresh 128-bit initialization vector is generated for every encrypted record.

Encryption requires a persisted database. Configuring an encryption key on a volatile (no-path) dataset throws MemDbConfigurationException at configuration time. Calling Insert(..., encrypt: true) on a database whose configuration has no encryption key throws NotEncryptionReadyException.

What encryption protects

Record-level encryption protects record contents at rest on disk. It does not protect:

  • Records held in the in-memory cache (any code in the same process can read decrypted records via the normal API)
  • Pointer metadata in the .map file (id, timestamps, encryption flag, length, position)
  • Index data (indexes are RAM-only and are built from decrypted record values)

If an attacker can read the raw .db file but cannot run code in the application’s process, the encrypted record bytes remain protected by AES-256-CBC.

Configuration

Configure encryption with either a password (hashed into a 256-bit AES key via SHA-256) or a raw 256-bit (32-byte) key:

// Configure with a password — minimum 10 characters
MemDb.ConfigureFor<Person>("people", _dbDirectory)
    .EncryptWithPassword(() => "SuperSecureEncryptionPasswordGoesHere.")
    .Register();

// Configure with a raw 256-bit key — must be exactly 32 bytes
MemDb.ConfigureFor<Person>("people", _dbDirectory)
    .EncryptWithKey(() => myAes256BitKey)
    .Register();

Constraints enforced at configuration or open time:

  • Passwords must be at least 10 characters long (MemDbConfiguration.MinPasswordLength)
  • Raw keys must be exactly 32 bytes (256 bits)
  • The provider delegate is invoked when the database is opened; supplying null or an empty password throws

Usage

Encrypt individual records at insert by passing encrypt: true:

db.Insert(eric, (id) => eric.Id = id, encrypt: true);  // encrypted at rest
db.Insert(kyle, (id) => kyle.Id = id);                 // stored unencrypted

Reads are transparent — encrypted records are decrypted once at cache load time, and all subsequent reads serve plaintext objects directly from the cache.

Encryption cannot be applied on Update. To encrypt an existing unencrypted record, delete and re-insert it.

Opening a database without the key

If a database is opened without the encryption key configured, encrypted records are silently skipped during the initial load. The cache holds only the unencrypted records; the encrypted bytes remain untouched on disk.

This enables a deferred-key workflow: open and operate on non-sensitive records without the key; when the user supplies the password, dispose the instance and re-open with the key configured to access the full dataset.

Counts and queries against a partially-loaded dataset reflect only the unencrypted subset; avoid totals or aggregates in this state.

Key rotation

The encryption key is a commitment made at database creation time. Switching keys later requires reading all records out of the old database (with the old key) and re-inserting them into a new database (with the new key).