-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture Encryption Model
title: "Architecture — Encryption Model" category: "architecture" version: "1.0" last_updated: "2024-01-15" standards: ["NIST-SP-800-53", "FSTEC", "ISA-IEC-62443"] related_pages: ["Architecture-Data-Fragmentation", "Architecture-Overview", "Database-Secrets-DB", "Security-Principles", "Operations-Vault-Init"] ai_summary: "VaultFlower encryption model. AES-256-GCM for data at rest, envelope encryption with DEK/KEK hierarchy, HashiCorp Vault as KMS, Shamir Secret Sharing for master key, mTLS for data in transit. Every encryption decision is mapped to NIST SP 800-53 and FSTEC controls."
VaultFlower uses a multi-layer encryption model that protects credentials at rest, in transit, and during assembly. The model is designed so that compromising any single component — a database, a service, or even the application code — does not expose plaintext credentials.
Layer 1: Data at rest → AES-256-GCM (per-record, unique IV)
Layer 2: Key protection → Envelope encryption (DEK encrypted by KEK)
Layer 3: Key storage → HashiCorp Vault (KEK never leaves Vault)
Layer 4: Vault protection → Shamir Secret Sharing (3-of-5 administrators)
Layer 5: Data in transit → TLS 1.3 + mTLS for RabbitMQ
Layer 6: Memory safety → Plaintext exists only in GC-managed memory
Every credential stored in the Secrets DB is encrypted using AES-256-GCM (Advanced Encryption Standard, 256-bit key, Galois/Counter Mode).
algorithm: AES-256-GCM
key_size: 256 bits
mode: GCM (Galois/Counter Mode)
iv_size: 96 bits (12 bytes) — NIST recommended for GCM
auth_tag_size: 128 bits (16 bytes)
properties:
confidentiality: true # Data is unreadable without DEK
integrity: true # GCM auth tag detects tampering
authenticity: true # Auth tag proves data origin
performance: high # Hardware acceleration on modern CPUs
why_gcm_over_cbc:
- GCM provides authenticated encryption (AEAD)
- CBC requires separate HMAC for integrity — more complexity, more risk
- GCM is parallelizable — better performance
- NIST SP 800-38D recommends GCM for authenticated encryptionEach credential record has its own unique IV (Initialization Vector):
credential_record:
username_encrypted: AES-256-GCM(DEK, IV_1, plaintext_username)
password_encrypted: AES-256-GCM(DEK, IV_2, plaintext_password)
iv: IV_2 (stored per record)
auth_tag: GCM authentication tag (stored per record)
Critical rule: IV is never reused with the same key. Each encryption operation generates a cryptographically random IV using System.Security.Cryptography.RandomNumberGenerator.
If IV were reused: Two ciphertexts encrypted with the same key and IV can be XOR'd to reveal plaintext. This is a catastrophic failure mode.
VaultFlower uses envelope encryption — a standard pattern where data is encrypted with a Data Encryption Key (DEK), and the DEK itself is encrypted with a Key Encryption Key (KEK).
┌─────────────────────────────────────────────────────────────┐
│ KEY HIERARCHY │
│ │
│ Master Key (MK) │
│ ├── Never leaves HashiCorp Vault │
│ ├── Protected by Shamir Secret Sharing (3-of-5) │
│ └── Encrypts: KEK │
│ │
│ Key Encryption Key (KEK) │
│ ├── Stored encrypted in Vault │
│ ├── Decryptable only by Vault using Master Key │
│ └── Encrypts: DEKs │
│ │
│ Data Encryption Keys (DEK) │
│ ├── One per database: │
│ │ vault/dek/identity → encrypts Identity DB data │
│ │ vault/dek/assets → encrypts Assets DB data │
│ │ vault/dek/secrets → encrypts Secrets DB data │
│ ├── Stored encrypted in Vault │
│ ├── Returned to application on request (with valid token) │
│ └── Encrypts: Actual credential data │
│ │
│ Ciphertext (in PostgreSQL) │
│ ├── AES-256-GCM encrypted credential │
│ ├── Unique IV per record │
│ └── GCM auth tag for integrity verification │
└─────────────────────────────────────────────────────────────┘
Problem with direct encryption:
If we encrypt all data with a single key, rotating that key
requires re-encrypting ALL data — potentially millions of records.
Also, if the key is compromised, ALL data is compromised.
Solution — Envelope Encryption:
To rotate a DEK:
1. Generate new DEK
2. Re-encrypt only the data encrypted with old DEK
3. Encrypt new DEK with KEK
4. Store new encrypted DEK in Vault
(Old data encrypted with old DEK can be migrated gradually)
To rotate KEK:
1. Decrypt all DEKs with old KEK
2. Generate new KEK
3. Re-encrypt all DEKs with new KEK
(Data itself is NOT re-encrypted — only DEKs are)
To rotate Master Key (rare):
1. Vault re-seals itself with new Shamir shares
2. KEKs are re-encrypted by Vault internally
HashiCorp Vault serves as the KMS (Key Management System) and trusted intermediary for all encryption operations.
vault/
├── dek/
│ ├── identity → DEK for Identity DB
│ ├── assets → DEK for Assets DB
│ └── secrets → DEK for Secrets DB
├── kek/
│ └── master → Master KEK (encrypted)
├── mfa/
│ ├── totp/
│ │ └── {user_id} → TOTP HMAC secret
│ ├── webauthn/
│ │ └── {credential_id} → WebAuthn public key
│ └── smartcard/
│ └── {user_id} → Smartcard UID hash + PIN hash
├── db/
│ ├── assets_connstring → PostgreSQL connection string (Assets)
│ ├── secrets_connstring → PostgreSQL connection string (Secrets)
│ └── identity_connstring → PostgreSQL connection string (Identity)
├── minio/
│ └── credentials → MinIO access credentials
├── rabbitmq/
│ └── credentials → RabbitMQ credentials + TLS certs
└── tokens/
└── assembly/
└── {checkout_id} → Time-bound assembly tokens
# vfw-api policy
path "vault/dek/*" {
capabilities = ["read"]
}
path "vault/mfa/*" {
capabilities = ["read", "create", "update"]
}
path "vault/tokens/assembly/*" {
capabilities = ["create", "update", "delete"]
}
# vfw-worker-workflow policy
path "vault/dek/secrets" {
capabilities = ["read"]
}
path "vault/tokens/assembly/*" {
capabilities = ["read", "delete"]
}
# No policy grants access to vault/kek/ or vault/db/
# to any application service — Vault manages these internallyHashiCorp Vault is sealed by default when it starts. It cannot serve any secrets until it is unsealed. VaultFlower uses Shamir Secret Sharing for unsealing:
Shamir configuration:
Total shares (N): 5 administrators
Threshold (K): 3 administrators required to unseal
How unsealing works:
1. Vault starts → sealed state (cannot serve secrets)
2. Administrator 1 provides their Shamir share + Smartcard + PIN
3. Administrator 2 provides their Shamir share + Smartcard + PIN
4. Administrator 3 provides their Shamir share + Smartcard + PIN
5. Vault reconstructs master key from 3 shares
6. Vault unseals → starts serving secrets
Security properties:
✓ Any 2 of 5 administrators cannot unseal (threshold = 3)
✓ Even if 2 administrators are compromised, Vault stays sealed
✓ No single administrator knows the master key
✓ Shamir shares are mathematically independent
✓ Smartcard + PIN required per administrator (physical possession)
Administrator 1: Share A + Smartcard A → Director of IT Security
Administrator 2: Share B + Smartcard B → CISO
Administrator 3: Share C + Smartcard C → Lead Security Engineer
Administrator 4: Share D + Smartcard D → Deputy CISO
Administrator 5: Share E + Smartcard E → Vault Custodian
Minimum for unseal: Any 3 of the 5 above
All internal service communication uses mutual TLS (mTLS) — both the client and server present certificates, and both verify each other.
vfw-pki (ICA01.contoso.com) — Intermediate CA
├── Issues: RabbitMQ server certificate
├── Issues: RabbitMQ client certificates (per service)
├── Issues: Vault TLS certificate
├── Issues: nginx TLS certificate
├── Issues: gMSA certificate for Rotation Agent
└── Issues: Smartcard certificates (if using PKI-based smartcards)
Certificate validation:
Every mTLS connection validates:
✓ Certificate issued by trusted CA (vfw-pki)
✓ Certificate not expired
✓ Certificate not revoked (OCSP check against vfw-pki)
✓ Common Name or SAN matches expected service identity
tls_configuration:
minimum_version: TLS_1_3
preferred_ciphers:
- TLS_AES_256_GCM_SHA384
- TLS_CHACHA20_POLY1305_SHA256
- TLS_AES_128_GCM_SHA256
certificate_rotation: automatic (before expiry via PKI)
ocsp_stapling: enabled
hsts: enabled (portal and API)
mtls_services:
rabbitmq:
server_cert: issued by vfw-pki
client_certs:
- vfw-dc-api
- vfw-dc-worker-workflow
- VaultFlower.Worker.Rotation
- MFA plugin containers
vault:
server_cert: issued by vfw-pki
client_cert: vfw-dc-api service certificatePassword history entries in secrets.password_history follow the same encryption model as current credentials:
history_record:
password_encrypted: AES-256-GCM(DEK_secrets, IV_N, old_plaintext)
vault_key_ref: path to DEK in Vault
iv: unique per history record
auth_tag: GCM authentication tag
History validation (new password generation):
1. Request DEK from Vault
2. Decrypt last N history entries (N = policy.history_count)
3. Compare new password hash against decrypted history
4. If match found → regenerate (max 10 attempts)
5. All operations happen in memory — history never stored as plaintext
The .NET 9 runtime provides garbage collection, but sensitive data in managed memory can persist until GC runs. VaultFlower mitigates this:
// Example: SecureString or byte[] zeroing after use
// (actual implementation will follow this pattern)
byte[] plaintextPassword = null;
try
{
plaintextPassword = vault.DecryptCredential(ciphertext, dek);
// Use password — display in UI, print to form
await DisplayPassword(plaintextPassword);
}
finally
{
// Zero out memory immediately after use
if (plaintextPassword != null)
{
CryptographicOperations.ZeroMemory(plaintextPassword);
}
}Platform note: System.Security.Cryptography.CryptographicOperations.ZeroMemory() is specifically designed to zero memory without being optimized away by the JIT compiler — a common pitfall with Array.Clear().
Signed task completion forms stored in MinIO are encrypted at the bucket level:
minio_encryption:
type: SSE-KMS (Server-Side Encryption with KMS)
kms: HashiCorp Vault (via MinIO KMS integration)
key_path: vault/minio/sse-key
per_object: true # Each object encrypted with unique data key
key_rotation: automatic (configurable, default: 90 days)| Standard | Control | Requirement | Implementation |
|---|---|---|---|
| NIST SP 800-53 | SC-12 | Cryptographic Key Management | Vault KEK hierarchy, Shamir sharing |
| NIST SP 800-53 | SC-13 | Cryptographic Protection | AES-256-GCM, TLS 1.3 |
| NIST SP 800-53 | SC-28 | Protection at Rest | Envelope encryption, per-DB DEK |
| NIST SP 800-53 | SC-28(1) | Cryptographic Protection at Rest | AES-256-GCM for all stored credentials |
| NIST SP 800-53 | SC-8 | Transmission Confidentiality | TLS 1.3 + mTLS |
| NIST SP 800-53 | SC-8(1) | Cryptographic Protection in Transit | mTLS, TLS 1.3 minimum |
| NIST SP 800-53 | IA-7 | Cryptographic Module Authentication | FIPS-validated .NET crypto primitives |
| FSTEC | ЗИ.3 | Cryptographic protection | AES-256-GCM, Vault KMS |
| FSTEC | ИАФ.5 | Protection of authenticators | TOTP secrets in Vault, smartcard refs |
| IEC 62443 | SR 4.3 | Use of cryptography | AES-256-GCM, TLS 1.3, mTLS |
| IEC 62443 | SR 4.1 | Information confidentiality | Envelope encryption, Vault |
Trigger: Scheduled (annually) or on suspected compromise
Process:
1. Generate new DEK
2. Store new DEK in Vault (new path)
3. Re-encrypt all records in affected DB with new DEK
(done in batches to avoid downtime)
4. Update vault_key_ref in all affected records
5. Delete old DEK from Vault
6. Audit: rotation.dek_rotated (INFO event)
Impact: Transparent to users (background operation)
Trigger: Scheduled (every 2 years) or on suspected compromise
Process:
1. Vault re-encrypts all DEKs with new KEK internally
2. No application changes required
3. No data re-encryption required
Impact: Vault downtime < 1 minute
Trigger: Administrator departure or on suspected compromise
Process:
1. Requires 3 of 5 current administrators with smartcards
2. Vault generates new master key
3. New Shamir shares distributed to administrators
(may include new administrators if someone departed)
4. Old shares immediately invalidated
5. Vault re-seals and re-unseals with new shares
Impact: Brief Vault unavailability during ceremony
- Architecture-Data-Fragmentation — why three databases
- Architecture-Overview — system context
- Database-Secrets-DB — encrypted credential schema
- Security-Principles — Principles 2 and 7
- Operations-Vault-Init — Vault initialization and Shamir ceremony
- ADR-002-Data-Fragmentation — decision record