Skip to content

DC-324(Feat): AES-256-GCM at-rest encryption for PII fields#273

Open
noxmwalsh wants to merge 15 commits into
mainfrom
feature/DC-324-encrypt-at-rest-user-table
Open

DC-324(Feat): AES-256-GCM at-rest encryption for PII fields#273
noxmwalsh wants to merge 15 commits into
mainfrom
feature/DC-324-encrypt-at-rest-user-table

Conversation

@noxmwalsh
Copy link
Copy Markdown
Contributor

@noxmwalsh noxmwalsh commented May 4, 2026

🔗 Jira ticket

DC-324

✍️ Description

Adds AES-256-GCM encryption at rest for reversible PII stored in the portal database, plus deterministic email lookup via EmailHash so we do not rely on searchable plaintext email.

Cryptography & configuration

  • New PiiEncryption settings (ActiveKeyId, key ring with Base64 256-bit material) in appsettings.json; mirrored in appsettings.Development.example.json for local setup.
  • IPiiSymmetricEncryption / PiiAesGcmSymmetricEncryption: AES-GCM envelopes with per-write nonce, embedded key id, sentinel prefix (sep-pii:v1:), tamper detection via GCM tag.
  • IEmailLookupHasher / EmailLookupHasher: HMAC-based normalized-email fingerprint for indexed equality lookups (aligns with existing identifier hashing patterns).
  • PiiEncryptionSettingsValidator: Validates configured keys and coherence with PiiEncryptionGuard (production rejects placeholder dev keys, analogous to identifier hasher guard).

Data model & migration

  • EF migration EncryptPiiAtRestColumnsAndEmailHash: encrypts relevant columns, adds EmailHash, adjusts constraints/indexes; DateOfBirth moved to string storage suitable for ciphertext (migration uses dynamic SQL where needed).
  • UserEncryptedFieldMapper centralizes encrypt/decrypt mapping for user entities.

Repositories & flows

  • DatabaseUserRepository: reads/writes encrypted fields; lookup by EmailHash with transitional handling for legacy plaintext rows; guards against duplicate emails when legacy plaintext still exists.
  • DatabaseDocVerificationChallengeRepository: encrypts ID-proofing challenge payloads at rest.

Startup

  • PiiPlaintextEncryptionBackfill: post-migration backfill of legacy plaintext → ciphertext and missing EmailHash (separate from operational key re-seal rotation — see ADR).
  • Wired from Program.cs after migrations.

Seeding

  • DataSeeder updated so bulk user adds respect existing emails via hash-aware checks and avoid silent duplicates next to legacy rows.

Documentation

  • docs/adr/0014-pii-encryption-at-rest.md: algorithm, key ring, rotation vs backfill, email lookup, logging constraints (no plaintext PII in logs).

Tests

  • Crypto: PiiAesGcmSymmetricEncryptionTests, PiiPlaintextEncryptionBackfillTests
  • Config/guards: PiiEncryptionSettingsValidatorTests, PiiEncryptionGuardTests
  • Repos/seeding: DatabaseUserRepositoryTests, doc challenge repo tests, DataSeederTests, DatabaseSeederTests, PortalDbContextTests
  • Shared test crypto helpers in TestPortalCryptography

🔗 Links to related PRs

✅ Completion tasks

  • Added relevant tests
  • Meets acceptance criteria in ticket
  • Configuration changes:
    • If new environment variables are added, update in Tofu
    • If new environment secrets are added, update in Tofu and set in AWS Secret Manager
    • If you're adding an appsetting, add it to the requisite example file, and update it in the AWS AppConfig
    • If appsetttings are changed, update in AWS AppConfig

Deploy / ops notes

  • Set real PiiEncryption key id(s) and key material per environment; do not use dev placeholders in production (PiiEncryptionGuard).
  • Coordinate AWS AppConfig (and any Tofu vars) for PiiEncryption before or with release.
  • After deploy, confirm migration + backfill logs show success; spot-check login and profile flows that read/write email, phone, DOB, and doc-verification challenge data.

@noxmwalsh noxmwalsh marked this pull request as ready for review May 5, 2026 00:28
Comment thread src/SEBT.Portal.Core/AppSettings/PiiEncryptionSettings.cs Outdated
@pkarman
Copy link
Copy Markdown
Collaborator

pkarman commented May 5, 2026

the ADR and algorithm pattern looks right to me.

The only suggestion I have is for planned expiration of PII. I.e. does it need to hang around forever in the db, or can it be deleted after some known period of time? While that may be out of scope for this particular PR, I can say that the only thing better than encrypting PII at rest is not storing PII at all.

@jamesmblair jamesmblair self-requested a review May 5, 2026 18:08
@noxmwalsh noxmwalsh self-assigned this May 5, 2026
Comment thread src/SEBT.Portal.Core/Services/IEmailLookupHasher.cs Outdated
Comment thread src/SEBT.Portal.Infrastructure/Services/PiiPlaintextEncryptionBackfill.cs Outdated
Copy link
Copy Markdown
Member

@jamesmblair jamesmblair left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks really good. Add a few minor comments.

@noxmwalsh noxmwalsh changed the title DC-324 Add: AES-256-GCM at-rest encryption for PII fields DC-324(Feat): AES-256-GCM at-rest encryption for PII fields May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants