docs/en/guides/08-encryption.md
This guide describes how to enable and use at-rest data encryption in OpenViking.
OpenViking provides transparent at-rest data encryption to ensure data security and isolation in multi-tenant environments:
See Data Encryption for conceptual explanations.
ov system crypto init-key --output ~/.openviking/master.key
Edit ~/.openviking/ov.conf:
{
"encryption": {
"enabled": true,
"provider": "local",
"local": {
"key_file": "~/.openviking/master.key"
}
},
"storage": {
"workspace": "./data"
}
}
import openviking as ov
import asyncio
async def test():
client = ov.AsyncOpenViking(path="./data")
await client.initialize()
# Add resource (automatically encrypted)
await client.add_resource("Hello, encrypted world!", reason="Test encryption")
# Read resource (automatically decrypted)
results = await client.find("encrypted")
print(f"Found {len(results)} results")
await client.close()
asyncio.run(test())
Done! Now all written data is automatically encrypted.
OpenViking provides two layers of encryption protection:
| Encryption Layer | Config | Algorithm | Reversible | Description |
|---|---|---|---|---|
| File Layer | encryption.enabled | AES-GCM | ✅ Yes | Protects entire storage files |
| API Key Field Layer | encryption.api_key_hashing.enabled | Argon2id | ❌ No | Protects API keys themselves |
Version Change: OpenViking v0.3.12 → later versions
Behavior Change:
encryption.enabled = true implicitly enabled API key Argon2id hashingencryption.api_key_hashing.enabledImpact:
encryption.enabled = true but encryption.api_key_hashing.enabled is not explicitly set to true, you will see the following warning log on startup:
API key hashing is disabled while file encryption is enabled.
Previously, encryption.enabled=true implicitly enabled API key Argon2id hashing.
Now, API keys will be stored in plaintext within AES-GCM encrypted files.
To maintain the previous behavior, set encryption.api_key_hashing.enabled=true.
Migration Options:
| Option | Config | Behavior |
|---|---|---|
| Maintain Previous Behavior | api_key_hashing.enabled = true | API keys stored using Argon2id hashing |
| Recommended New Behavior | api_key_hashing.enabled = false (default) | API keys stored in plaintext (file layer still encrypted) |
By default, encryption.api_key_hashing.enabled = false:
encryption.enabled = true, the entire file is protected by AES-GCM encryptionov admin list-users can display the full API keyFor maximum API key protection, you can enable Argon2id one-way hashing:
{
"encryption": {
"enabled": true,
"api_key_hashing": {
"enabled": true
}
}
}
Note: When enabled:
ov admin list-users only shows key_prefix instead of the full API key{
"encryption": {
"enabled": true,
"provider": "local",
"local": {
"key_file": "~/.openviking/master.key"
},
"api_key_hashing": {
"enabled": false
}
}
}
| Provider | Use Case | Pros | Cons |
|---|---|---|---|
| Local | Dev environments, single-node | Simple, no external services | Key stored locally, less secure |
| Vault | Production, multi-cloud | Enterprise-grade KMS, version control | Requires deploying and maintaining Vault |
| Volcengine KMS | Volcengine cloud | Cloud-native KMS service | Volcengine-only |
# Generate and save to specified path
ov system crypto init-key --output ~/.openviking/master.key
# Or use short option
ov system crypto init-key -o ~/.openviking/master.key
Output example:
✓ Root key generated successfully
✓ Saved to: /Users/you/.openviking/master.key
master.key safe600 (owner-only read/write){
"encryption": {
"enabled": true,
"provider": "local",
"local": {
"key_file": "~/.openviking/master.key"
}
}
}
vault secrets enable transit
# KV v2 (recommended)
vault secrets enable -version=2 kv
# Or KV v1
vault secrets enable kv
{
"encryption": {
"enabled": true,
"provider": "vault",
"vault": {
"address": "https://vault.example.com:8200",
"token": "hvs.xxxxxxxxxxxxxxxxxxxxx",
"mount_point": "transit",
"kv_mount_point": "secret",
"kv_version": 1,
"root_key_name": "openviking-root-key",
"encrypted_root_key_key": "openviking-encrypted-root-key"
}
}
}
Configuration Parameters:
| Parameter | Description | Default |
|---|---|---|
address | Vault server address | Required |
token | Vault authentication token | Required |
mount_point | Transit engine mount path | "transit" |
kv_mount_point | KV engine mount path | "secret" |
kv_version | KV engine version (1 or 2) | 1 |
root_key_name | Key name in Transit engine | "openviking-root-key" |
encrypted_root_key_key | Path to store encrypted root key in KV engine | "openviking-encrypted-root-key" |
Configure minimal permissions for the Token:
path "transit/encrypt/openviking-root" {
capabilities = ["update"]
}
path "transit/decrypt/openviking-root" {
capabilities = ["update"]
}
AES_256{
"encryption": {
"enabled": true,
"provider": "volcengine_kms",
"volcengine_kms": {
"key_id": "d926aa0d-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"region": "cn-beijing",
"access_key": "AKLTxxxxxxxxxxxxxxxxxx",
"secret_key": "Tmpxxxxxxxxxxxxxxxxxxxxxx",
"endpoint": null,
"key_file": "~/.openviking/openviking-volcengine-root-key.enc"
}
}
}
Configuration Parameters:
| Parameter | Description | Default |
|---|---|---|
key_id | KMS Key ID | Required |
region | Region | Required |
access_key | Access Key | Required |
secret_key | Secret Key | Required |
endpoint | Custom KMS endpoint (optional) | null (use default endpoint) |
key_file | Local cache file path for encrypted root key | "~/.openviking/openviking-volcengine-root-key.enc" |
Configure minimal permissions for the Access Key:
{
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt"
],
"Resource": [
"trn:kms:*:*:key/d926aa0d-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
]
}
]
}
Encrypted files start with magic number OVE1:
# View first 4 bytes
hexdump -C ./data/agfs/your-file | head -1
Encrypted file:
00000000 4f 56 45 31 01 01 00 00 00 20 8a 7b 2c 9d 1e |OVE1..... .{,..|
(First 4 bytes are 4f 56 45 31 = "OVE1")
Unencrypted file:
00000000 7b 22 63 6f 6e 74 65 6e 74 73 22 3a 5b 7b 22 70 |{"contents":[{"p|
Try decrypting with different providers — it should fail (this is normal security behavior):
# Encrypt with Provider A
encrypted = await provider_a.encrypt_file_key(plaintext, "test-account")
# Try decrypting with Provider B (should fail)
try:
await provider_b.decrypt_file_key(encrypted, "test-account")
print("❌ Security vulnerability: Cross-provider decryption succeeded!")
except Exception as e:
print("✓ Secure: Cross-provider decryption failed as expected")
import openviking as ov
import asyncio
async def migrate():
client = ov.AsyncOpenViking(path="./data")
await client.initialize()
# List all resources
resources = await client.list_resources()
for resource in resources:
# Read old resource (unencrypted)
content = await client.read_resource(resource["uri"])
# Re-write (automatically encrypted)
await client.add_resource(content, reason="Migrate to encrypted storage")
await client.close()
asyncio.run(migrate())
Note: This is a destructive operation, recommend testing first in a staging environment.
Error: Key file not found: ~/.openviking/master.key
Solution:
~ is properly expanded (use expanduser())Error: Failed to connect to Vault
Solution:
address configurationError: Invalid credentials
Solution:
Error: KeyMismatchError
Explanation: This is expected security behavior. Different providers use different root keys and cannot decrypt each other's data.
If using encrypted files created with an older OpenViking version, partial reads may return ciphertext.
Solution: Upgrade to the latest OpenViking version.