WitDatabase provides built-in encryption to protect your data at rest. All data stored on disk can be encrypted using industry-standard algorithms, ensuring that even if someone gains access to the database file, they cannot read the contents without the encryption key.


1. Overview

Why Encrypt Your Database?

Data at rest (stored on disk) is vulnerable to various threats:

Threat Description Encryption Protects?
Stolen device Laptop, phone, or server physically stolen ✅ Yes
Backup exposure Database backup files accessed by unauthorized party ✅ Yes
Disk disposal Old hard drives not properly wiped ✅ Yes
File system access Attacker gains read access to file system ✅ Yes
Memory dump Attacker reads process memory ⚠️ Partial (keys in memory)
Active attack Attacker has application credentials ❌ No (they have the key)

Encryption is one layer of a defense-in-depth strategy. It protects against physical access but doesn't replace proper access controls, authentication, and network security.

How WitDatabase Encryption Works

WitDatabase uses authenticated encryption (AEAD — Authenticated Encryption with Associated Data):

[[Svg Src="./witdatabase-encryption-process.svg" Alt="witdatabase-encryption-process"]]

Key features of WitDatabase encryption:

  • Page-level encryption: Each database page is encrypted independently
  • Authenticated encryption: Tampering is detected and rejected
  • Transparent operation: Encryption/decryption happens automatically
  • No plaintext on disk: All data, indexes, and metadata are encrypted

Available Algorithms

WitDatabase supports two encryption algorithms:

Algorithm Package Hardware Accel Best For
AES-256-GCM Built-in ✅ AES-NI Desktop, server (Intel/AMD)
ChaCha20-Poly1305 BouncyCastle ❌ Software Blazor WASM, ARM, mobile

AES-256-GCM is the default and recommended choice for most applications. It's hardware-accelerated on modern Intel and AMD processors, providing excellent performance.

ChaCha20-Poly1305 is ideal when hardware acceleration isn't available, such as in Blazor WebAssembly (browser) or on some ARM devices without AES-NI instructions.

Quick Start

Enable encryption with a single line:

// Via connection string
"Data Source=secure.witdb;Encryption=aes-gcm;Password=MySecretPassword123!"

// Via builder pattern
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithEncryption("MySecretPassword123!")
    .Build();

// ChaCha20 (for Blazor WASM)
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithBouncyCastleEncryption("MySecretPassword123!")
    .Build();

Once enabled, encryption is completely transparent — you use the database exactly as before:

// Normal operations — encryption happens automatically
db.Put("user:1"u8, userData);
var data = db.Get("user:1"u8);

// SQL works the same way
engine.Execute("INSERT INTO Users (Name) VALUES ('John')");
var users = engine.Query("SELECT * FROM Users");

2. Encryption Algorithms

AES-256-GCM (Default)

AES-256-GCM (Advanced Encryption Standard with Galois/Counter Mode) is the default encryption algorithm. It's widely trusted, standardized, and hardware-accelerated on most modern processors.

Characteristics:

Property Value
Algorithm AES-256-GCM
Key size 256 bits (32 bytes)
Nonce size 96 bits (12 bytes)
Auth tag 128 bits (16 bytes)
Hardware accel AES-NI (Intel/AMD)

When to use AES-GCM:

  • Desktop and server applications
  • Any environment with AES-NI support
  • When maximum performance is required
  • Default choice for most applications

Configuration:

// Connection string
"Data Source=secure.witdb;Encryption=aes-gcm;Password=MyPassword123!"

// Builder pattern — password-based
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithEncryption("MyPassword123!")  // AES-GCM is default
    .Build();

// Builder pattern — raw key
byte[] key = new byte[32];
RandomNumberGenerator.Fill(key);

var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithAesEncryption(key)
    .Build();

ChaCha20-Poly1305

ChaCha20-Poly1305 is an alternative algorithm that doesn't rely on hardware acceleration. It provides comparable security to AES-GCM and excellent performance in software.

Characteristics:

Property Value
Algorithm ChaCha20-Poly1305
Key size 256 bits (32 bytes)
Nonce size 96 bits (12 bytes)
Auth tag 128 bits (16 bytes)
Hardware accel None (fast in software)
Requires OutWit.Database.Core.BouncyCastle package

When to use ChaCha20:

  • Blazor WebAssembly (no AES-NI in browser)
  • ARM devices without AES instructions
  • When consistent performance across platforms matters
  • When you prefer software-only crypto

Installation:

<PackageReference Include="OutWit.Database.Core.BouncyCastle" Version="1.0.0" />

Configuration:

// Connection string
"Data Source=secure.witdb;Encryption=chacha20-poly1305;Password=MyPassword123!"

// Builder pattern — password-based
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithBouncyCastleEncryption("MyPassword123!")
    .Build();

// Builder pattern — raw key
byte[] key = new byte[32];
RandomNumberGenerator.Fill(key);

var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithBouncyCastleEncryption(key)
    .Build();

Algorithm Comparison

Aspect AES-256-GCM ChaCha20-Poly1305
Security Excellent Excellent
With AES-NI ⭐⭐⭐⭐⭐ Fastest ⭐⭐⭐ Fast
Without AES-NI ⭐⭐⭐ Fast ⭐⭐⭐⭐ Faster
Blazor WASM ⭐⭐ Slow ⭐⭐⭐⭐ Fast
Memory usage Lower Slightly higher
Dependencies Built-in BouncyCastle
FIPS compliant Yes No

Performance benchmarks (encrypting 1000 4KB pages):

Environment AES-256-GCM ChaCha20-Poly1305
Intel i7 (AES-NI) 15 ms 45 ms
ARM without AES 120 ms 60 ms
Blazor WASM 800 ms 200 ms

Choosing an Algorithm

[[Svg Src="./witdatabase-encryption-decision.svg" Alt="witdatabase-encryption-decision"]]

Simple rule: Use AES-GCM unless you're targeting Blazor WASM or ARM without AES-NI.


3. Key Derivation

Encryption requires a cryptographic key. WitDatabase supports both password-based encryption (where a key is derived from a password) and raw key encryption (where you provide the key directly).

Password-Based Key Derivation

When you provide a password, WitDatabase uses PBKDF2 (Password-Based Key Derivation Function 2) to convert it into a cryptographic key:

[[Svg Src="./witdatabase-key-derivation.svg" Alt="witdatabase-key-derivation"]]

Why PBKDF2?

  • Slows down brute-force attacks (100,000 iterations = 100,000x slower)
  • Each password/salt combination produces a unique key
  • Industry-standard algorithm (NIST SP 800-132)

Configuration:

// Simple password — salt derived from password
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithEncryption("MyPassword123!")
    .Build();

// Password with user — different salt per user
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithEncryption("admin", "AdminPassword!")  // user + password
    .Build();

// Connection string equivalents
"Data Source=secure.witdb;Encryption=aes-gcm;Password=MyPassword123!"
"Data Source=secure.witdb;Encryption=aes-gcm;User=admin;Password=AdminPassword!"

User-Based Salt Derivation

When you provide both a username and password, WitDatabase derives a unique salt from the username. This is useful for multi-tenant scenarios:

[[Svg Src="./witdatabase-tenant-key-derivation.svg" Alt="witdatabase-tenant-key-derivation"]]

Even if two tenants use the same password, they get different encryption keys:

// Tenant 1
var db1 = new WitDatabaseBuilder()
    .WithFilePath("tenant1.witdb")
    .WithEncryption("tenant1", "SharedPassword")
    .Build();

// Tenant 2 — same password, different key!
var db2 = new WitDatabaseBuilder()
    .WithFilePath("tenant2.witdb")
    .WithEncryption("tenant2", "SharedPassword")
    .Build();

Fast Key Derivation (WASM)

PBKDF2 with 100,000 iterations can be slow in Blazor WebAssembly. WitDatabase offers a "fast" mode with fewer iterations for browser environments:

Mode Iterations Time (typical) Security
Normal 100,000 50-100 ms Maximum
Fast 10,000 5-10 ms Reduced

When to use fast mode:

  • Blazor WebAssembly where startup time matters
  • Low-power devices where PBKDF2 is very slow
  • When password is already strong (long, random)

Configuration:

// Connection string
"Data Source=app.witdb;Encryption=aes-gcm;Password=MyPassword;Fast Encryption=true"

// Builder pattern — AES-GCM
var db = new WitDatabaseBuilder()
    .WithFilePath("app.witdb")
    .WithEncryptionFast("MyPassword")  // 10,000 iterations
    .Build();

// Builder pattern — ChaCha20
var db = new WitDatabaseBuilder()
    .WithFilePath("app.witdb")
    .WithBouncyCastleEncryptionFast("user", "password")
    .Build();

Security warning: Fast mode reduces protection against brute-force attacks. Only use it when:

  • Startup time is critical
  • Password is strong (20+ characters, random)
  • Other security measures are in place

Raw Key Encryption

For maximum security and performance, you can provide a 256-bit key directly:

// Generate a random key (do this once, store securely!)
byte[] key = new byte[32];  // 256 bits
RandomNumberGenerator.Fill(key);

// Use the key
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithAesEncryption(key)  // or .WithBouncyCastleEncryption(key)
    .Build();

Advantages of raw keys:

  • No PBKDF2 overhead — instant startup
  • Key can be stored in HSM, Key Vault, or secure element
  • No password to brute-force

Key management considerations:

  • You are responsible for key storage and protection
  • Key loss = data loss (no recovery possible)
  • Use a key management system (Azure Key Vault, AWS KMS, HashiCorp Vault)

Key Derivation Best Practices

Password selection:

  • Use strong passwords (12+ characters, mixed case, numbers, symbols)
  • Consider using passphrases ("correct horse battery staple" style)
  • Never hardcode passwords in source code

Key storage:

// BAD: Hardcoded password
var db = new WitDatabaseBuilder()
    .WithEncryption("MyHardcodedPassword")  // ❌ Don't do this!
    .Build();

// GOOD: From environment variable
var password = Environment.GetEnvironmentVariable("DB_PASSWORD")
    ?? throw new InvalidOperationException("DB_PASSWORD not set");
var db = new WitDatabaseBuilder()
    .WithEncryption(password)
    .Build();

// GOOD: From configuration
var password = Configuration["Database:Password"];

// GOOD: From secure storage (Azure Key Vault, etc.)
var key = await keyVault.GetSecretAsync("database-key");

Multi-tenant applications:

  • Always use user-based salt derivation
  • Each tenant's data is encrypted with a unique key
  • Tenant isolation is maintained even with shared passwords

4. Working with Encrypted Databases

Creating an Encrypted Database

When you create a database with encryption enabled, WitDatabase:

  1. Derives the encryption key from your password (or uses the provided key)
  2. Generates a unique salt for the database
  3. Stores encryption metadata in the database header
  4. Encrypts all subsequent data
// Create encrypted database
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithEncryption("MyPassword123!")
    .WithBTree()
    .Build();

// Use normally — encryption is transparent
db.Put("secret"u8, sensitiveData);
db.Flush();
db.Dispose();

The resulting file (secure.witdb) is completely encrypted. Opening it without the correct password will fail.

Opening an Encrypted Database

To open an existing encrypted database, provide the same password:

// Open with correct password
var db = WitDatabase.Open("secure.witdb", "MyPassword123!");

// Or via builder
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithEncryption("MyPassword123!")
    .Build();

// Read data
var data = db.Get("secret"u8);

If you provide the wrong password, WitDatabase throws an exception:

try
{
    var db = WitDatabase.Open("secure.witdb", "WrongPassword");
}
catch (CryptographicException ex)
{
    Console.WriteLine("Invalid password or corrupted database");
}

Detecting Encrypted Databases

You can check if a database is encrypted before opening it:

var info = WitDatabase.GetDatabaseInfo("database.witdb");

if (info.IsEncrypted)
{
    Console.WriteLine(
Loading...
quot;Database is encrypted with: {info.EncryptionProvider}"); // info.EncryptionProvider is "aes-gcm" or "chacha20-poly1305" // Prompt for password var password = PromptForPassword(); var db = WitDatabase.Open("database.witdb", password); } else { // Open without password var db = WitDatabase.Open("database.witdb"); }

Encryption with ADO.NET

ADO.NET connections work the same way:

// Create encrypted database
using var connection = new WitDbConnection(
    "Data Source=secure.witdb;Encryption=aes-gcm;Password=MyPassword123!");
connection.Open();

// Create tables, insert data — all encrypted automatically
using var cmd = connection.CreateCommand();
cmd.CommandText = "CREATE TABLE Secrets (Id INT PRIMARY KEY, Data TEXT)";
cmd.ExecuteNonQuery();

cmd.CommandText = "INSERT INTO Secrets (Id, Data) VALUES (1, 'Top Secret Information')";
cmd.ExecuteNonQuery();

Encryption with Entity Framework Core

EF Core works transparently with encrypted databases:

public class AppDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseWitDb("Data Source=app.witdb;Encryption=aes-gcm;Password=MyPassword");
    }
    
    public DbSet<Secret> Secrets { get; set; }
}

// Use normally
using var context = new AppDbContext();
context.Secrets.Add(new Secret { Data = "Sensitive information" });
await context.SaveChangesAsync();

Changing Passwords

WitDatabase doesn't support changing passwords directly (it would require re-encrypting the entire database). To change the password:

  1. Export data from the old database
  2. Create a new database with the new password
  3. Import data into the new database
  4. Delete the old database
public async Task ChangePasswordAsync(string oldPath, string oldPassword, 
                                       string newPath, string newPassword)
{
    // Open old database
    await using var oldDb = WitDatabase.Open(oldPath, oldPassword);
    
    // Create new database with new password
    await using var newDb = new WitDatabaseBuilder()
        .WithFilePath(newPath)
        .WithEncryption(newPassword)
        .WithBTree()
        .Build();
    
    // Copy all data
    foreach (var (key, value) in oldDb.Scan(Array.Empty<byte>(), null))
    {
        await newDb.PutAsync(key, value);
    }
    
    await newDb.FlushAsync();
    
    // Delete old database (securely!)
    File.Delete(oldPath);
}

Encryption and Blazor WebAssembly

In Blazor WASM, use ChaCha20-Poly1305 for better performance:

@inject IJSRuntime JSRuntime

private WitDatabase? _db;

protected override async Task OnInitializedAsync()
{
    _db = new WitDatabaseBuilder()
        .WithIndexedDbStorage("MyApp", JSRuntime)
        .WithBTree()
        .WithBouncyCastleEncryptionFast("user", userPassword)  // ChaCha20, fast mode
        .Build();
    
    await ((StorageIndexedDb)_db.Store).InitializeAsync();
}

Blazor-specific considerations:

  • Use ChaCha20 (no AES-NI in browser)
  • Use fast mode if startup time matters
  • Data is stored in IndexedDB, encrypted
  • Encryption key is in browser memory (clear on logout)

5. Custom Crypto Providers

WitDatabase's encryption is built on a pluggable provider architecture. You can implement your own ICryptoProvider to use custom encryption algorithms, hardware security modules (HSM), or specialized crypto libraries.

The ICryptoProvider Interface

public interface ICryptoProvider : IProvider, IDisposable
{
    /// <summary>
    /// Encrypts plaintext using AEAD.
    /// </summary>
    void Encrypt(ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> plaintext, 
                 Span<byte> ciphertext, Span<byte> tag);

    /// <summary>
    /// Decrypts ciphertext using AEAD. Returns false if authentication fails.
    /// </summary>
    bool Decrypt(ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> ciphertext, 
                 ReadOnlySpan<byte> tag, Span<byte> plaintext);

    /// <summary>
    /// Creates a clone with the same key (for multi-threaded access).
    /// </summary>
    ICryptoProvider Clone();

    /// <summary>
    /// Nonce size in bytes (typically 12).
    /// </summary>
    int NonceSize { get; }

    /// <summary>
    /// Authentication tag size in bytes (typically 16).
    /// </summary>
    int TagSize { get; }

    /// <summary>
    /// Unique identifier for this provider (e.g., "aes-gcm").
    /// </summary>
    string ProviderKey { get; }
}

Example: HSM Integration

Here's an example of integrating with a Hardware Security Module:

public sealed class HsmCryptoProvider : ICryptoProvider
{
    private readonly IHsmClient _hsm;
    private readonly string _keyId;
    private bool _disposed;
    
    public HsmCryptoProvider(IHsmClient hsm, string keyId)
    {
        _hsm = hsm ?? throw new ArgumentNullException(nameof(hsm));
        _keyId = keyId ?? throw new ArgumentNullException(nameof(keyId));
    }
    
    public string ProviderKey => "hsm-aes-gcm";
    public int NonceSize => 12;
    public int TagSize => 16;
    
    public void Encrypt(ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> plaintext, 
                       Span<byte> ciphertext, Span<byte> tag)
    {
        ThrowIfDisposed();
        
        // Call HSM for encryption
        var result = _hsm.EncryptAesGcm(_keyId, nonce.ToArray(), plaintext.ToArray());
        
        result.Ciphertext.AsSpan().CopyTo(ciphertext);
        result.Tag.AsSpan().CopyTo(tag);
    }
    
    public bool Decrypt(ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> ciphertext, 
                       ReadOnlySpan<byte> tag, Span<byte> plaintext)
    {
        ThrowIfDisposed();
        
        try
        {
            var result = _hsm.DecryptAesGcm(_keyId, 
                nonce.ToArray(), ciphertext.ToArray(), tag.ToArray());
            result.AsSpan().CopyTo(plaintext);
            return true;
        }
        catch (AuthenticationException)
        {
            return false;
        }
    }
    
    public ICryptoProvider Clone() => new HsmCryptoProvider(_hsm, _keyId);
    
    public void Dispose()
    {
        if (_disposed) return;
        _disposed = true;
        // HSM client cleanup if needed
    }
    
    private void ThrowIfDisposed()
    {
        ObjectDisposedException.ThrowIf(_disposed, this);
    }
}

Registering a Custom Provider

Register your provider with the ProviderRegistry:

// Register at application startup
ProviderRegistry.Instance.Register<ICryptoProvider>(
    "hsm-aes-gcm",
    parameters =>
    {
        var hsmClient = parameters.GetRequired<IHsmClient>("hsmClient");
        var keyId = parameters.GetRequired<string>("keyId");
        return new HsmCryptoProvider(hsmClient, keyId);
    });

// Use via connection string (after registration)
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithEncryption("hsm-aes-gcm", new ProviderParameters()
        .Set("hsmClient", myHsmClient)
        .Set("keyId", "production-key-001"))
    .Build();

Using ModuleInitializer for Auto-Registration

For seamless integration, use [ModuleInitializer]:

public static class HsmCryptoRegistration
{
    private static bool _initialized;
    
    [ModuleInitializer]
    public static void Initialize()
    {
        if (_initialized) return;
        
        ProviderRegistry.Instance.Register<ICryptoProvider>(
            "hsm-aes-gcm",
            parameters => new HsmCryptoProvider(
                parameters.GetRequired<IHsmClient>("hsmClient"),
                parameters.GetRequired<string>("keyId")));
        
        _initialized = true;
    }
}

Creating Builder Extensions

Make your provider easy to use with extension methods:

public static class WitDatabaseBuilderHsmExtensions
{
    public static WitDatabaseBuilder WithHsmEncryption(
        this WitDatabaseBuilder builder, 
        IHsmClient hsm, 
        string keyId)
    {
        var provider = new HsmCryptoProvider(hsm, keyId);
        builder.Options.CustomCryptoProvider = provider;
        return builder;
    }
}

// Usage
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithHsmEncryption(hsmClient, "my-key-id")
    .Build();

Example: Azure Key Vault Integration

Here's a sketch of Azure Key Vault integration:

public sealed class AzureKeyVaultCryptoProvider : ICryptoProvider
{
    private readonly KeyClient _keyClient;
    private readonly CryptographyClient _cryptoClient;
    
    public AzureKeyVaultCryptoProvider(string keyVaultUrl, string keyName)
    {
        var credential = new DefaultAzureCredential();
        _keyClient = new KeyClient(new Uri(keyVaultUrl), credential);
        _cryptoClient = new CryptographyClient(
            _keyClient.GetKey(keyName).Value.Id, credential);
    }
    
    public string ProviderKey => "azure-keyvault";
    public int NonceSize => 12;
    public int TagSize => 16;
    
    public void Encrypt(ReadOnlySpan<byte> nonce, ReadOnlySpan<byte> plaintext, 
                       Span<byte> ciphertext, Span<byte> tag)
    {
        // Azure Key Vault encryption call
        // Note: This is simplified — real implementation needs proper AEAD handling
        var result = _cryptoClient.Encrypt(EncryptionAlgorithm.A256Gcm, 
            plaintext.ToArray(), nonce.ToArray());
        // Copy results...
    }
    
    // ... Decrypt, Clone, Dispose implementations
}

Provider Implementation Guidelines

When implementing a custom ICryptoProvider:

Security:

  • Always use authenticated encryption (AEAD)
  • Clear sensitive data (keys) on dispose
  • Use CryptographicOperations.ZeroMemory() for clearing
  • Validate input parameters
  • Return false from Decrypt on authentication failure (don't throw)

Performance:

  • Use Span<T> efficiently — avoid allocations
  • Consider using ArrayPool<T> for temporary buffers
  • Make Clone() efficient for multi-threaded scenarios

Compatibility:

  • Use standard nonce and tag sizes (12 and 16 bytes)
  • Ensure ProviderKey is unique and descriptive
  • Test with both B+Tree and LSM-Tree engines

6. Security Best Practices

Password Security

Strong passwords:

// WEAK: Short, simple, guessable
"password123"     // ❌
"admin"           // ❌
"database2024"    // ❌

// STRONG: Long, complex, random
"K8$mP2@nL5#vQ9&xR3*wJ7"              // ✅ Random characters
"correct-horse-battery-staple-42!"    // ✅ Passphrase
// 32+ characters recommended for maximum security

Never hardcode passwords:

// BAD
var db = WitDatabase.Open("data.witdb", "MyHardcodedPassword");

// GOOD: Environment variable
var password = Environment.GetEnvironmentVariable("DB_PASSWORD")
    ?? throw new InvalidOperationException("DB_PASSWORD not set");
var db = WitDatabase.Open("data.witdb", password);

// GOOD: Configuration with secrets
var password = Configuration["Database:Password"]; // From appsettings.json or secrets.json

// GOOD: User input
Console.Write("Enter database password: ");
var password = ReadPasswordSecurely();
var db = WitDatabase.Open("data.witdb", password);

Key Management

For raw keys:

// Generate a strong key (do once, store securely)
byte[] key = new byte[32];
RandomNumberGenerator.Fill(key);

// Store in secure location:
// - Azure Key Vault
// - AWS Secrets Manager
// - HashiCorp Vault
// - Hardware Security Module (HSM)
// - Encrypted configuration

// Load from secure storage
byte[] key = await keyVault.GetSecretAsync("database-encryption-key");
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithAesEncryption(key)
    .Build();

Key rotation (future databases):

// WitDatabase doesn't support in-place key rotation
// To rotate keys:
// 1. Export data
// 2. Create new database with new key
// 3. Import data
// 4. Securely delete old database

Memory Security

Encryption keys exist in memory while the database is open. To minimize exposure:

// Clear password from memory after use
var password = GetPasswordFromUser();
try
{
    var db = WitDatabase.Open("data.witdb", password);
    // Use database...
}
finally
{
    // Clear password (if using char[])
    if (password is char[] chars)
    {
        Array.Clear(chars, 0, chars.Length);
    }
}

Consider:

  • Minimize database open time in sensitive applications
  • Close database when not actively needed
  • Use secure memory features of your platform if available

Backup Security

Encrypted backups:

// The database file is already encrypted
// Simply copy the file for backup
File.Copy("secure.witdb", "backup/secure.witdb.bak");

// Or use streaming
await using var source = File.OpenRead("secure.witdb");
await using var dest = File.Create("backup/secure.witdb.bak");
await source.CopyToAsync(dest);

Backup key management:

  • Store backup passwords/keys separately from backups
  • Use different keys for different backup tiers (daily, weekly, archive)
  • Document key recovery procedures
  • Test restore procedures regularly

Secure Deletion

When deleting encrypted databases:

// Standard deletion (file may be recoverable)
File.Delete("secure.witdb");

// More secure: overwrite first (not perfect on SSDs)
var fileInfo = new FileInfo("secure.witdb");
var length = fileInfo.Length;

await using (var stream = File.Open("secure.witdb", FileMode.Open, FileAccess.Write))
{
    var zeros = new byte[4096];
    while (stream.Position < length)
    {
        var toWrite = (int)Math.Min(zeros.Length, length - stream.Position);
        await stream.WriteAsync(zeros.AsMemory(0, toWrite));
    }
}

File.Delete("secure.witdb");

// Best: Use full-disk encryption (BitLocker, FileVault, LUKS)
// so deletion is automatic when disk is wiped

Audit Logging

Log encryption-related events (without logging keys or passwords!):

public class SecureDatabaseService
{
    private readonly ILogger _logger;
    
    public WitDatabase OpenDatabase(string path, string password)
    {
        _logger.LogInformation("Opening encrypted database: {Path}", path);
        
        try
        {
            var db = WitDatabase.Open(path, password);
            _logger.LogInformation("Successfully opened encrypted database: {Path}", path);
            return db;
        }
        catch (CryptographicException)
        {
            _logger.LogWarning("Failed to open encrypted database (wrong password?): {Path}", path);
            throw;
        }
    }
}

7. Quick Reference

Connection String Properties

Property Values Description
Encryption none, aes-gcm, chacha20-poly1305 Encryption algorithm
Password string Encryption password
User string Username for salt derivation
Fast Encryption true, false Reduced PBKDF2 iterations

Builder Methods

AES-GCM (built-in):

Method Description
.WithEncryption(password) Password-based AES-GCM
.WithEncryption(user, password) User + password AES-GCM
.WithEncryptionFast(password) Fast mode (fewer iterations)
.WithAesEncryption(key) Raw 256-bit key

ChaCha20-Poly1305 (BouncyCastle):

Method Description
.WithBouncyCastleEncryption(password) Password-based ChaCha20
.WithBouncyCastleEncryption(user, password) User + password ChaCha20
.WithBouncyCastleEncryptionFast(user, password) Fast mode
.WithBouncyCastleEncryption(key) Raw 256-bit key

Algorithm Comparison

Aspect AES-256-GCM ChaCha20-Poly1305
Package Built-in OutWit.Database.Core.BouncyCastle
Key size 256 bits 256 bits
Nonce size 12 bytes 12 bytes
Tag size 16 bytes 16 bytes
Hardware accel AES-NI None
Best for Desktop, server WASM, ARM

Key Derivation

Mode Iterations Use Case
Normal 100,000 Default, maximum security
Fast 10,000 Blazor WASM, low-power devices

Common Patterns

// Create encrypted database
var db = new WitDatabaseBuilder()
    .WithFilePath("secure.witdb")
    .WithEncryption("password")
    .Build();

// Open encrypted database
var db = WitDatabase.Open("secure.witdb", "password");

// Check if encrypted
var info = WitDatabase.GetDatabaseInfo("db.witdb");
if (info.IsEncrypted) { /* prompt for password */ }

// Blazor WASM
var db = new WitDatabaseBuilder()
    .WithIndexedDbStorage("App", JSRuntime)
    .WithBouncyCastleEncryptionFast("user", "password")
    .Build();