Skip to content

Microsoft.Data.Sqlite: Support byte[] / ReadOnlySpan<byte> for encryption key to enable secure memory handling #38097

@rajsin31415

Description

@rajsin31415

Summary
SqliteConnectionStringBuilder.Password and the internal PRAGMA key path only accept string. This means callers who generate cryptographic keys as byte arrays (via RandomNumberGenerator.GetBytes, DPAPI, Keychain, etc.) are forced to convert to a Base64 or hex string, losing the ability to securely zero the key material after use.

.NET strings are immutable, GC-managed, and cannot be deterministically scrubbed — the runtime may copy them during compaction, and they persist in memory until collected. This defeats native-side secure-zeroization that encrypted SQLite providers (SQLCipher, sqlite3mc) implement in their allocators and cipher cleanup paths.

Motivation
The underlying SQLite C API already accepts binary key material:

int sqlite3_key(sqlite3 *db, const void *pKey, int nKey);
int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey);
The current string-only surface forces a lossy round-trip:

byte[] key → string (cannot zero) → PRAGMA key = '...' (more string copies) → sqlite3_key(void*, int)
This is inconsistent with the rest of the .NET cryptographic API surface, where sensitive key material is handled as byte[] or ReadOnlySpan:

Rfc2898DeriveBytes — accepts and returns byte[]
Aes.Create().Key — byte[]
ProtectedData.Protect/Unprotect — byte[] in, byte[] out
CryptographicOperations.ZeroMemory — operates on Span
RandomNumberGenerator.GetBytes — produces byte[]
Microsoft.Data.Sqlite is the gap in this chain. A caller who carefully generates, uses, and zeros key material everywhere else still cannot zero the copy that lives inside the connection string.

Practical impact
Applications that use encrypted SQLite for local persistence (credential caches, encrypted user data stores, offline-first apps) typically derive keys from platform key storage (DPAPI, macOS Keychain, Android Keystore). These systems return byte[]. The forced string conversion means:

Multiple immutable copies of the key exist on the managed heap
Copies survive in memory after the connection is closed and the byte[] source is zeroed
Note: SecureString is deprecated and not encrypted on non-Windows platforms, so that is not a viable alternative.

Proposed API
Add byte[] or ReadOnlySpan overloads for key material. Possible shapes:

// Option A: New property on SqliteConnectionStringBuilder
public byte[]? PasswordBytes { get; set; }

// Option B: Method on SqliteConnection
public void SetPassword(ReadOnlySpan key);
public void ChangePassword(ReadOnlySpan key);

// Option C: Both
Internally, these would call sqlite3_key(db, pKey, nKey) directly rather than formatting a PRAGMA key = '...' string, which is a more faithful mapping to the underlying C API.

Additional context
This came up during a security audit of a desktop application that uses Microsoft.Data.Sqlite with the sqlite3mc encrypted SQLite provider.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions