Skip to content

Refactor SSN encryption: per-record salt and deterministic HMAC lookup #89

Description

@coderabbitai

Background

This issue tracks the planned refactoring of SSN encryption, identified during review of PR #80 (comment: #80 (comment)).

Requested by: @codebyNorthsteep

Problem

The current implementation uses Encryptors.text(encryptionKey, salt) (Spring Security) with a single static salt stored as an environment variable. Because Encryptors.text() generates a fresh random 16-byte IV per encryption call:

  1. Uniqueness checks are unreliable: staffRepository.existsBySocialSecurityNumber(encryptionService.encrypt(ssn)) will almost never match an existing row because each encryption of the same SSN yields a different ciphertext.
  2. Update comparison is broken: Comparing the stored encrypted SSN against a newly encrypted value will always differ.

The current workaround (findAll() + decrypt-and-compare) is O(n) and will degrade as the dataset grows. It is marked with TODO comments in EmploymentFormService.

Planned Solution (as agreed in PR #80)

  1. Per-record random salt: Generate a unique salt for each SSN at encryption time and store it alongside the encrypted value in the database, so decryption remains possible without a shared static salt.
  2. Deterministic HMAC lookup column: Add a ssn_lookup column to the Staff and EmploymentForm entities containing a keyed HMAC-SHA256 of the plaintext SSN (using a separate, dedicated HMAC key from env vars). This restores O(1) existence checks.
  3. Service updates: Refactor EmploymentFormService.validateSsnNotExists() and updateFormBeforeApproval() to query/compare using the HMAC lookup value instead of the full encrypted ciphertext.
  4. Key rotation strategy: Document how the HMAC key and encryption key can be rotated.

Acceptance Criteria

  • Per-record salt is generated and persisted alongside each encrypted SSN
  • A ssn_lookup (HMAC-SHA256) column is added and populated on create/update
  • validateSsnNotExists() uses existsBySsnLookup(computeHmac(ssn))
  • updateFormBeforeApproval() compares HMAC values for change detection
  • Static env-var salt is no longer used as the sole salt for all encryptions
  • Unit tests updated to reflect the new approach (no more stubbing encrypt() for uniqueness checks)
  • Key rotation strategy is documented

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions