core contains the CloakDF class - a pseudonymisation/anonymisation engine for consistent cross-table key replacement and column masking, with Fernet-encrypted storage of mappings.
Setup & data
We use the Northwind dataset — a classic sample database with customers, orders, and employees tables that share keys across them.
import httpxfrom pathlib import Pathbase ="https://raw.githubusercontent.com/neo4j-contrib/northwind-neo4j/refs/heads/master/data"files = ["customers.csv", "orders.csv", "employees.csv"]dest = Path("../data")dest.mkdir(exist_ok=True)for f in files: (dest/f).write_bytes(httpx.get(f"{base}/{f}").content)print(f"✓ {f}")
✓ customers.csv
✓ orders.csv
✓ employees.csv
import pandas as pdfor f in files: df = pd.read_csv(dest/f, on_bad_lines='skip')print(f"\n{f}: {df.shape}")print(df.columns.tolist()) display(df.head(2))
Each table is configured with key groups (id1, id2, …) and a list of columns to mask. Key groups ensure consistent pseudonymisation across tables — e.g. customerID maps to the same UUID in both customers and orders because both use group id1.
UUID4 is generated from cryptographically random bits, no timestamp, MAC address, or sequence baked in. The fake ID reveals nothing about the original. With 122 random bits, collision probability is astronomically small.
Shared vault for masked columns
Masked columns (names, addresses, etc.) use a flat shared dict across all tables. If "Maria Anders" appears in both customers and orders, it gets the same hex token. This preserves referential consistency without leaking the original value.
setdefault pattern
Both key_maps and vault use dict.setdefault() , a get-or-insert in one call. This ensures existing mappings are reused and new values are only generated for unseen originals:
km =self.key_maps.setdefault(group, {}) # create or retrieve inner dictkm.setdefault(val, str(uuid.uuid4())) # only insert if missing
Encryption at rest with Fernet
The mappings (key_maps + vault) are the most sensitive artefact ; they’re the keys to de-anonymise everything. We use Fernet (AES-128-CBC + HMAC) from the cryptography library. The encryption key is never stored on the CloakDF instance — it’s passed in at save()/load() time, keeping the class stateless with respect to secrets.