Skip to content

fix: core masking correctness (FF3-1, double-encryption, rekey, detection, fail-loud API)#33

Merged
fcarvajalbrown merged 11 commits into
mainfrom
fix/core-correctness
Jul 2, 2026
Merged

fix: core masking correctness (FF3-1, double-encryption, rekey, detection, fail-loud API)#33
fcarvajalbrown merged 11 commits into
mainfrom
fix/core-correctness

Conversation

@fcarvajalbrown

Copy link
Copy Markdown
Owner

Situation

A high-effort multi-agent review of the MaskOps core (not the marketing tooling) surfaced ten verified correctness defects in the masking engine itself — several of which contradict the compliance guarantees the docs advertise and would surface in a buyer's due-diligence review.

Task

Fix the confirmed defects across all three masking modes (asterisk, FPE, consistent) plus detection and API-ergonomics issues, with regression tests and cross-implementation verification where cryptography is involved. Product-level decisions (FF3-1 approach, PE_DNI default, rekey API) were confirmed with the owner before implementing.

Action

  • FF3-1 is now real. Replaced the nonstandard two-AES construction with conformant NIST FF3-1 (single AES-256, REVB conventions, 56-bit tweak split) and enforced the FF3-1 minimum domain. Verified byte-for-byte against an independent FF3-1 library and NIST FF1 sample vectors, embedded as permanent regression tests.
  • No more double-encryption. A shared claim-set threads through the FPE and consistent pipelines so a later pattern never re-encrypts a span an earlier one masked (Argentine DNI via pe_dni, bare CPF via pesel). All digit maskers are now length-preserving via a single shared helper, keeping the documented original format.
  • Rekey rotates real tokens. rekey_pii_fpe now rotates separator-bearing tokens (SSN, phone, RUT) instead of silently leaving them under the old key, fails loudly on un-rotatable cells, and takes a pattern= argument so families that keep a check digit / country prefix rotate exactly.
  • Loud failures instead of silent unmasking. Unknown patterns= names, unknown mask_pii mode values, and misused salt now raise (ComputeError / ValueError) instead of returning data unmasked.
  • Detection fixes. IBAN validated with ISO 7064 mod-97 + print-format + 34-char length; compressed/letter-only IPv6 detected; MBI character classes corrected to CMS; pe_dni context-gated in the default pipeline.

Result

  • 56 Rust + 422 Python tests pass, including new regression tests for every fix.
  • FPE tokens are now interoperable with conformant FF3-1 tooling, and FPE-mode reversibility holds for the ID families that previously double-encrypted.
  • Breaking: FF3-1 output differs from prior releases (re-mask requires the old version to decrypt first), FPE rejects sub-6-digit runs, and the new raise-on-misuse behavior changes error surfaces. These are recorded under Unreleased in the changelog. No version bump is included — releasing (likely a major, given the breaking changes) is a separate decision.

…ttern stages

Thread a shared claim-set through the FPE and consistent pipelines so a later
pattern never re-encrypts a span an earlier pattern already masked (e.g.
Argentine DNI re-matched by pe_dni, bare CPF re-matched by pesel). All digit
maskers are now length-preserving via reinsert_digits, keeping byte offsets
stable across stages and preserving the documented original format.
…-rotatable cells

mask_pii_fpe_rekey treated the whole cell as one digit string, so any token
carrying a separator (SSN, phone, RUT, ...) failed to parse and was silently
returned under the OLD key. Rekey now extracts a cell's encrypted digits, rotates
them, and reinserts with the original format. A new pattern= argument names the
column's token family so rut/co_nit (check digit) and phone (country prefix),
which leave some digits unencrypted, rotate exactly. Cells that cannot be rotated
now raise instead of passing through unrotated.
BREAKING CHANGE: FF3-1 output differs from prior releases (tokens masked by an
older version must be decrypted with that version before re-masking); FPE now
rejects digit runs shorter than 6 digits (the FF3-1 minimum domain); unknown
patterns= entries and unknown mask_pii mode values now raise instead of
returning data unmasked; and CPF/CNPJ/card FPE output is now format-preserving.
These changes break the 2.x API contract and warrant a 3.0.0 release.
@fcarvajalbrown fcarvajalbrown merged commit 6090206 into main Jul 2, 2026
5 checks passed
@fcarvajalbrown fcarvajalbrown deleted the fix/core-correctness branch July 2, 2026 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant