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:
- Derives the encryption key from your password (or uses the provided key)
- Generates a unique salt for the database
- Stores encryption metadata in the database header
- 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:
- Export data from the old database
- Create a new database with the new password
- Import data into the new database
- 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();