SimpleVecDB supports at-rest encryption for both SQLite metadata and usearch index files.
Overview
- SQLite encryption: Uses SQLCipher for transparent page-level AES-256 encryption with hardware acceleration (AES-NI)
- Index file encryption: Uses AES-256-GCM to encrypt usearch HNSW index files
- Zero runtime overhead: Index files are decrypted on load and encrypted on save; search operations have no crypto overhead
Installation
pip install simplevecdb[encryption]
This installs:
- sqlcipher3-binary - SQLCipher Python bindings
- cryptography - AES-GCM implementation
Basic Usage
from simplevecdb import VectorDB
# Create encrypted database with a passphrase
db = VectorDB("secure.db", encryption_key="my-secret-passphrase")
# Use normally - encryption is transparent
collection = db.collection("documents")
collection.add_texts(
["Confidential document content"],
embeddings=[[0.1] * 384]
)
# Save encrypts the index file
db.save()
db.close()
# Reopen with the same key
db = VectorDB("secure.db", encryption_key="my-secret-passphrase")
results = db.collection("documents").similarity_search([0.1] * 384, k=5)
Key Management
Passphrase vs Raw Key
# Option 1: Passphrase (string) - internally derived to 32-byte key
db = VectorDB("secure.db", encryption_key="my-secret-passphrase")
# Option 2: Raw 32-byte key (more secure, use with a key management system)
import os
raw_key = os.urandom(32) # Generate once, store securely
db = VectorDB("secure.db", encryption_key=raw_key)
Best Practices
- Never hardcode keys - Use environment variables or a secrets manager
- Use strong passphrases - At least 20 characters with mixed case/numbers/symbols
- Backup your key - If you lose the key, the data is unrecoverable
- Consider key rotation - Re-encrypt periodically for compliance
import os
from simplevecdb import VectorDB
# Load key from environment
encryption_key = os.environ.get("SIMPLEVECDB_KEY")
if not encryption_key:
raise ValueError("SIMPLEVECDB_KEY environment variable not set")
db = VectorDB("secure.db", encryption_key=encryption_key)
Storage Layout
With encryption enabled, files are stored as:
mydb.db # SQLCipher encrypted SQLite database
mydb.db.default.usearch.enc # AES-256-GCM encrypted usearch index
When opened, the index is decrypted to memory (or a temp file). On save() or close(), the index is re-encrypted.
Performance
Search Operations
Encryption has zero overhead during search because: - SQLCipher uses page-level encryption with AES-NI hardware acceleration - Index files are decrypted once on load, then used directly from memory
Load/Save Operations
| Operation | Overhead |
|---|---|
| Database open | ~10-50ms for key derivation |
| Index load (10k vectors, 384 dim) | ~50-100ms decrypt |
| Index save | ~50-100ms encrypt |
| Search | 0ms (no crypto) |
Error Handling
from simplevecdb import VectorDB, EncryptionError, EncryptionUnavailableError
try:
db = VectorDB("secure.db", encryption_key="wrong-key")
except EncryptionError as e:
print(f"Failed to open encrypted database: {e}")
try:
db = VectorDB("secure.db", encryption_key="secret")
except EncryptionUnavailableError:
print("Install with: pip install simplevecdb[encryption]")
Common Errors
| Error | Cause | Solution |
|---|---|---|
EncryptionUnavailableError |
Missing dependencies | pip install simplevecdb[encryption] |
EncryptionError: wrong key |
Incorrect passphrase | Use the correct key |
ValueError: In-memory |
Used :memory: with encryption |
Use a file path |
Limitations
- In-memory databases cannot be encrypted - Encryption is for at-rest data
- Key cannot be changed - To change keys, export data and re-import
- Performance on large indexes - Decryption on load may take several seconds for 100k+ vectors
Security Notes
- SQLCipher uses AES-256-CBC with HMAC-SHA512 for authentication
- Index encryption uses AES-256-GCM with random 96-bit nonces
- Key derivation uses PBKDF2-SHA256 with 480,000 iterations (OWASP 2023 recommendation)
- The encryption key is held in memory during database usage
API Reference
simplevecdb.encryption
Encryption support for SimpleVecDB.
Provides at-rest encryption for both SQLite metadata (via SQLCipher) and usearch index files (via AES-256-GCM).
Design principles: - Zero performance overhead during search operations - Index files are encrypted only at rest (decrypt on load, encrypt on save) - SQLCipher provides transparent page-level encryption with AES-NI acceleration
Usage
from simplevecdb import VectorDB
Enable encryption with a passphrase
db = VectorDB("secure.db", encryption_key="my-secret-passphrase")
Or with raw bytes (32 bytes for AES-256)
db = VectorDB("secure.db", encryption_key=os.urandom(32))
Requirements
Included in the standard install. If missing, reinstall: pip install --force-reinstall simplevecdb
EncryptionError
EncryptionUnavailableError
Bases: ImportError
Raised when encryption dependencies are not installed.
Source code in src/simplevecdb/encryption.py
create_encrypted_connection(path, key, *, check_same_thread=False, timeout=30.0)
Create an encrypted SQLite connection using SQLCipher.
SQLCipher provides transparent AES-256 encryption at the page level, with hardware acceleration on CPUs supporting AES-NI.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Database file path (cannot be ":memory:" for encryption) |
required |
key
|
str | bytes
|
Encryption passphrase or 32-byte raw key |
required |
check_same_thread
|
bool
|
SQLite thread-safety setting |
False
|
timeout
|
float
|
Connection timeout in seconds |
30.0
|
Returns:
| Type | Description |
|---|---|
Connection
|
sqlite3.Connection with encryption enabled |
Raises:
| Type | Description |
|---|---|
EncryptionUnavailableError
|
If sqlcipher3 is not installed |
EncryptionError
|
If encryption setup fails |
ValueError
|
If trying to encrypt an in-memory database |
Source code in src/simplevecdb/encryption.py
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | |
is_database_encrypted(path)
Check if a database file is encrypted.
Attempts to open with standard sqlite3. If it fails with "not a database", the file is likely encrypted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Path to database file |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if database appears to be encrypted |
Source code in src/simplevecdb/encryption.py
encrypt_file(input_path, output_path, key)
Encrypt a file using AES-256-GCM.
File format: - 12 bytes: nonce - N bytes: ciphertext - 16 bytes: GCM auth tag (appended by cryptography)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_path
|
Path
|
Path to plaintext file |
required |
output_path
|
Path
|
Path for encrypted output |
required |
key
|
bytes
|
32-byte encryption key |
required |
Raises:
| Type | Description |
|---|---|
EncryptionUnavailableError
|
If cryptography is not installed |
EncryptionError
|
If encryption fails |
Source code in src/simplevecdb/encryption.py
decrypt_file(input_path, output_path, key)
Decrypt a file encrypted with encrypt_file().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_path
|
Path
|
Path to encrypted file |
required |
output_path
|
Path
|
Path for decrypted output |
required |
key
|
bytes
|
32-byte encryption key |
required |
Raises:
| Type | Description |
|---|---|
EncryptionUnavailableError
|
If cryptography is not installed |
EncryptionError
|
If decryption fails (wrong key, corrupted data) |