Skip to content

Architecture Encryption Model

Maks Zaikin edited this page Jun 29, 2026 · 1 revision

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."

🔑 Architecture — Encryption Model

Overview

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

AES-256-GCM — Data Encryption

Every credential stored in the Secrets DB is encrypted using AES-256-GCM (Advanced Encryption Standard, 256-bit key, Galois/Counter Mode).

Why AES-256-GCM?

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 encryption

Per-Record Encryption

Each 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.


Envelope Encryption — Key Hierarchy

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                │
└─────────────────────────────────────────────────────────────┘

Why Envelope Encryption?

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 — Key Management System

HashiCorp Vault serves as the KMS (Key Management System) and trusted intermediary for all encryption operations.

Vault Secrets Structure

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

Vault Access Policies

# 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 internally

Shamir Secret Sharing — Vault Unseal

HashiCorp 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)

Shamir Share Distribution

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

mTLS — Encryption in Transit

All internal service communication uses mutual TLS (mTLS) — both the client and server present certificates, and both verify each other.

Certificate Infrastructure

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

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 certificate

Password History Encryption

Password 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

Memory Safety

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().


Encryption at MinIO (Document Storage)

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)

Compliance Mapping

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

Key Rotation Procedures

DEK Rotation (per database)

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)

KEK Rotation

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

Master Key Rotation (Shamir Re-key)

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

Related Pages

Clone this wiki locally