Encryption & KMS
Purple8 Graph encrypts every node property value and edge property value at rest using AES-256-GCM with a unique IV per value. Encryption is transparent — your Cypher queries, REST API calls, and CLI commands all work unchanged.
Architecture: envelope encryption
┌─────────────────────────────────────────────────────────────┐
│ Node / Edge property value (plaintext) │
│ ↓ encrypt with AES-256-GCM (unique IV per value) │
│ Ciphertext envelope [P8KM v1 | key_id | iv | tag | data] │
│ ↓ key wrapping │
│ Data Encryption Key (DEK) — stored encrypted by KEK │
│ ↓ Key Encryption Key (KEK) lives in your KMS │
└─────────────────────────────────────────────────────────────┘The storage layer only ever sees ciphertext. The engine decrypts on read, re-encrypts on write. Your KMS holds the KEK — Purple8 never stores the master key.
Enabling encryption
from purple8_graph import GraphEngine
engine = GraphEngine(
"./data",
kms_provider="local", # or vault, aws, gcp, azure
kms_key_id="my-key",
kms_local_keystore="./keystore.json", # local only
)Or via environment variables (recommended for production):
KMS_PROVIDER=aws
KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/mrk-abc123
AWS_REGION=us-east-1Provider setup
Local file (development)
The simplest provider — key material is stored in a JSON file on disk. Do not use in production.
engine = GraphEngine(
"./data",
kms_provider="local",
kms_key_id="dev-key",
kms_local_keystore="./dev-keystore.json",
)KMS_PROVIDER=local
KMS_KEY_ID=dev-key
KMS_LOCAL_KEYSTORE=./keystore.jsonHashiCorp Vault
engine = GraphEngine(
"./data",
kms_provider="vault",
kms_key_id="purple8/graph/data-key",
)KMS_PROVIDER=vault
KMS_KEY_ID=purple8/graph/data-key
VAULT_ADDR=https://vault.internal:8200
VAULT_TOKEN=hvs.xxxxxxxxxxxx
# Optional mTLS:
VAULT_CACERT=/certs/ca.pem
VAULT_CLIENT_CERT=/certs/client.pem
VAULT_CLIENT_KEY=/certs/client.keyVault policy required (Transit secrets engine):
path "transit/encrypt/purple8-graph-data-key" {
capabilities = ["update"]
}
path "transit/decrypt/purple8-graph-data-key" {
capabilities = ["update"]
}
path "transit/rotate/purple8-graph-data-key" {
capabilities = ["update"]
}AWS KMS
engine = GraphEngine(
"./data",
kms_provider="aws",
kms_key_id="arn:aws:kms:us-east-1:123456789012:key/mrk-abc123",
)KMS_PROVIDER=aws
KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/mrk-abc123
AWS_REGION=us-east-1
# Credentials via IAM role (recommended) or:
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEYMinimum IAM policy:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["kms:GenerateDataKey", "kms:Decrypt", "kms:DescribeKey"],
"Resource": "arn:aws:kms:us-east-1:123456789012:key/mrk-abc123"
}]
}Google Cloud KMS
engine = GraphEngine(
"./data",
kms_provider="gcp",
kms_key_id="projects/my-project/locations/us-east1/keyRings/graph/cryptoKeys/data-key",
)KMS_PROVIDER=gcp
KMS_KEY_ID=projects/my-project/locations/us-east1/keyRings/graph/cryptoKeys/data-key
GOOGLE_APPLICATION_CREDENTIALS=/secrets/sa-key.json
# Or use Workload Identity (recommended for GKE)Required IAM role: roles/cloudkms.cryptoKeyEncrypterDecrypter
Azure Key Vault
engine = GraphEngine(
"./data",
kms_provider="azure",
kms_key_id="https://my-vault.vault.azure.net/keys/graph-key/abc123",
)KMS_PROVIDER=azure
KMS_KEY_ID=https://my-vault.vault.azure.net/keys/graph-key/abc123
AZURE_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
AZURE_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Or use Managed Identity (recommended for Azure VMs/AKS):
# AZURE_USE_MSI=trueRequired Key Vault permission: Key Vault Crypto User (encrypt + decrypt, no key management)
Zero-downtime key rotation
Rotation creates a new KEK version. Existing ciphertext is re-wrapped lazily on next write — reads still work because the engine stores the key_id in every ciphertext envelope.
from purple8_graph.kms import rotate_key
# Returns immediately. Re-wrapping happens in background.
rotate_key(engine, new_kms_key_id="arn:aws:kms:us-east-1:...key/mrk-NEW")Or via CLI:
purple8-graph kms rotate \
--new-key-id "arn:aws:kms:us-east-1:123456789012:key/mrk-NEW"WARNING
Key rotation is a background operation. Until re-wrapping completes, both the old and new KEK versions must be active in your KMS. Check progress with purple8-graph kms status.
Verify encryption status
# Check which provider is active and how many values are encrypted
purple8-graph kms status --db ./data
# Example output:
# Provider: aws
# Key ID: arn:aws:kms:us-east-1:...key/mrk-abc123
# Nodes encrypted: 48,291 / 48,291 (100%)
# Edges encrypted: 201,847 / 201,847 (100%)
# Pending re-wrap: 0Selecting fields for encryption
By default, all property values are encrypted. You can opt specific labels or properties out of encryption for performance-sensitive, non-sensitive fields:
engine = GraphEngine(
"./data",
kms_provider="aws",
kms_key_id="...",
encryption_exclude=[
"Node.timestamp", # never sensitive, high-cardinality
"Edge.weight", # numeric metric
],
)Environment variable reference
| Variable | Description | Required |
|---|---|---|
KMS_PROVIDER | local / vault / aws / gcp / azure | Yes |
KMS_KEY_ID | Provider-specific key identifier | Yes |
KMS_LOCAL_KEYSTORE | Path to JSON keystore (local provider only) | Local only |
VAULT_ADDR | HashiCorp Vault server URL | Vault only |
VAULT_TOKEN | Vault token | Vault only |
VAULT_CACERT | Vault CA certificate path | Vault (mTLS) |
AWS_REGION | AWS region for KMS | AWS only |
AWS_ACCESS_KEY_ID | AWS access key (prefer IAM role) | AWS (key auth) |
AWS_SECRET_ACCESS_KEY | AWS secret key (prefer IAM role) | AWS (key auth) |
GOOGLE_APPLICATION_CREDENTIALS | Path to GCP service account JSON | GCP (SA auth) |
AZURE_TENANT_ID | Azure Active Directory tenant ID | Azure |
AZURE_CLIENT_ID | Azure app registration client ID | Azure |
AZURE_CLIENT_SECRET | Azure app registration client secret | Azure |
AZURE_USE_MSI | Use Managed Identity instead of client secret | Azure (MSI) |