Skip to content

Column-Level AES-256-GCM Encryption & Blind Indexing

The GERCIA Platform enforces field-level cryptographic protection for sensitive personal identifiable information (PII) and protected health information (PHI). Even in the event of an adversary obtaining raw physical access to PostgreSQL data files (/data/db), storage snapshots, or database dumps, sensitive health and identity records remain computationally unreadable.


Brazilian regulatory frameworks mandate strict defense-in-depth measures for patient health data:

  • LGPD (Lei 13.709/2018, Art. 46): Requires security, technical, and administrative measures capable of safeguarding personal data from unauthorized access or accidental/unlawful destruction.
  • CFM Resolution 1.821/2007 (Electronic Health Records): Mandates cryptographic integrity and confidentiality for medical history, clinical diagnoses, and electronic prescriptions.
  • Civil Tax Identifiers: Patient CPF, guardian CPF, prescriber CPF, employee CPF.
  • Health Card Identifiers: Cartão Nacional de Saúde (CNS) 15-digit identifiers.
  • Sensitive Medical History: Document attachments, clinical notes, patient allergies, and prescription dosages.

2. Cryptographic Architecture: AES-256-GCM

Section titled “2. Cryptographic Architecture: AES-256-GCM”

Data is encrypted using AES-256-GCM (Advanced Encryption Standard in Galois/Counter Mode), an authenticated encryption with associated data (AEAD) algorithm:

flowchart LR
    subgraph ENCRYPT ["Encryption Pipeline (C# -> PostgreSQL)"]
        PLAINTEXT["Plaintext Value<br>(e.g. '123.456.789-00')"]
        NONCE["Cryptographic Nonce<br>(96-bit Unique IV)"]
        KEY["Tenant AES-256 Key<br>(HKDF Derived)"]
        AAD["Associated Data<br>(TenantId + EntityId)"]
        
        PLAINTEXT & NONCE & KEY & AAD --> GCM["AES-256-GCM Engine"]
        GCM --> CIPHER["Base64 Payload:<br>[Nonce (12B) | Tag (16B) | Ciphertext]"]
    end
  • Cipher Algorithm: AES-256-GCM (System.Security.Cryptography.AesGcm).
  • Key Length: 256 bits (32 bytes).
  • Nonce / IV: 96 bits (12 bytes), cryptographically random per encryption operation via RandomNumberGenerator.GetBytes(12). Nonces are never reused under the same key.
  • Authentication Tag: 128 bits (16 bytes). Verifies ciphertext integrity before decryption; any bit tampering results in an immediate cryptographic exception.
  • Associated Data (AAD): TenantId concatenated with EntityId is bound into the authentication tag calculation, preventing cross-tenant or cross-entity ciphertext relocation attacks.

GERCIA avoids a single global database encryption key. A breach of one tenant’s derived key cannot compromise any other tenant’s data.

Keys are derived using HKDF (HMAC-based Key Derivation Function, RFC 5869) via SHA-256:

TenantKey = HKDF-Expand(PRK = MasterKey, Info = TenantId, Length = 32)
flowchart TD
    GSM["Google Secret Manager<br>(GERCIA_APP_ENCRYPTION_KEY)"] --> HKDF["HKDF-SHA256 Derivation Engine"]
    TENANT["Current TenantId (UUIDv7)"] --> HKDF
    HKDF --> DERIVED["Unique 256-Bit Tenant Key<br>(Cached in Memory / 15-Min TTL)"]
  • Root Key Custody: The master key is stored in Google Secret Manager (GERCIA_APP_ENCRYPTION_KEY) and loaded into host memory at container bootstrap.
  • Deterministic Isolation: Each tenant has an isolated cryptographic domain derived mathematically from the master secret and the tenant’s immutable TenantId.

4. EF Core Transparent Value Converter ([Encrypted])

Section titled “4. EF Core Transparent Value Converter ([Encrypted])”

Developers annotate sensitive domain entity properties with the [Encrypted] attribute:

public class Patient : AggregateRoot, IMustHaveTenant
{
[Encrypted]
public string Cpf { get; private set; } = string.Empty;
[Encrypted]
public string CnsNumber { get; private set; } = string.Empty;
}

During EF Core model initialization (OnModelCreating), an entity configuration convention discovers all [Encrypted] properties and registers an AesGcmValueConverter:

  • On Persist (SaveChanges): Plaintext is serialized to UTF-8, encrypted via AES-256-GCM with the active tenant key, and written to PostgreSQL as Base64 text.
  • On Materialize (Query): Base64 payload is unpacked, authenticated, decrypted in memory, and populated onto the C# domain entity.

5. Searchable Encryption: Blind Indexing Pattern

Section titled “5. Searchable Encryption: Blind Indexing Pattern”

Because AES-256-GCM produces randomized ciphertext for the same plaintext (due to unique 96-bit nonces), standard SQL equality lookups (WHERE cpf = @input) cannot query the encrypted column directly.

GERCIA implements the Blind Index Pattern to enable high-speed indexed equality searches without exposing plaintext:

flowchart TD
    INPUT["Input Search Parameter<br>(e.g. CPF: '12345678900')"] --> NORM["Normalize String<br>(Strip punctuation & whitespace)"]
    NORM --> HMAC["HMAC-SHA256 Engine"]
    SALT["Blind Index Salt<br>(Secret from GSM)"] --> HMAC
    HMAC --> HASH["Deterministic Blind Index Hash<br>(64-character hex string)"]
    
    HASH --> QUERY["SQL Query:<br>SELECT * FROM clinical.patients<br>WHERE cpf_hash = @hash"]
    QUERY --> BTREE["PostgreSQL B-Tree Index<br>(idx_patients_cpf_hash)"]
CREATE TABLE clinical.patients (
id uuid NOT NULL,
tenant_id uuid NOT NULL,
cpf text NOT NULL, -- AES-256-GCM encrypted ciphertext
cpf_hash varchar(64) NOT NULL, -- HMAC-SHA256 deterministic blind index
full_name varchar(255) NOT NULL,
CONSTRAINT pk_patients PRIMARY KEY (id)
);
-- Fast B-Tree index on the blind hash scoped to the tenant
CREATE INDEX idx_patients_tenant_cpf_hash
ON clinical.patients (tenant_id, cpf_hash);
  1. One-Way Trapdoor: The blind index cannot be reversed to discover the patient’s CPF.
  2. Frequency Attack Resistance: The HMAC uses a distinct secret salt stored in Google Secret Manager (BLIND_INDEX_SALT), preventing rainbow table matching.
  3. High Performance: Queries execute standard index seeks in O(log N) time with zero decryption overhead inside PostgreSQL.