Skip to main content

Pseudonymisation/anonymisation engine with encrypted mapping storage

Project description

cloakdf

Install

pip install cloakdf

How to use

We’ll demonstrate cloakdf using the Northwind dataset. A classic sample database with customers, orders, and employees tables that share keys across them.

#| eval: false
import httpx
from pathlib import Path

base = "https://raw.githubusercontent.com/neo4j-contrib/northwind-neo4j/refs/heads/master/data"
files = ["customers.csv", "orders.csv", "employees.csv"]
data = Path("../data")
data.mkdir(exist_ok=True)

for f in files:
    (data/f).write_bytes(httpx.get(f"{base}/{f}").content)

1. Define your table configuration

Each table’s config specifies two kinds of columns:

  • id groups (e.g. id1, id2) — key columns that are shared across tables. Columns in the same group get consistent pseudonyms: customerID in both customers and orders maps to the same UUID, preserving referential integrity.

Note: All id columns must be string dtype before encoding — cast with .astype(str) if needed. - mask — sensitive columns to replace with opaque hex tokens. These are stored in a shared vault, so identical values (e.g. the same address appearing twice) get the same token.

tables = {
    'customers': {
        'id1': 'customerID',
        'mask': ['contactName', 'address', 'phone', 'companyName', 'fax', 'city']
    },
    'orders': {
        'id1': 'customerID',
        'id2': 'employeeID',
        'mask': ['shipName', 'shipAddress']
    },
    'employees': {
        'id2': 'employeeID',
        'mask': ['firstName', 'lastName', 'address', 'homePhone', 'birthDate', 'notes']
    },
}

2. Encode your DataFrames

import pandas as pd
from pathlib import Path

data = Path("../data")
unk = CloakDF(tables)

originals, encoded = {}, {}
for name in tables:
    df = pd.read_csv(data/f"{name}.csv", on_bad_lines='skip')
    for k, v in tables[name].items():
        if k.startswith('id'): df[v] = df[v].astype(str)
    originals[name] = df
    encoded[name] = unk.encode(name, df)

encoded['customers'].head(3)
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style>
customerID companyName contactName contactTitle address city region postalCode country phone fax
0 9eb2c3d4-32d3-434c-863c-44d8ac4067d9 9288db060f26 5c1dfdfc8f62 Sales Representative 80e2676376a1 c1f7bd6a22f0 NaN 12209 Germany 715e53fda07f cd0efa7df34e
1 82d494d5-a9e7-4690-b5ec-3104dad5b87d 18db1aa91f8c 3c4420341c1a Owner 135d66291b39 42d81129a68e NaN 05021 Mexico a5611db2c349 25fabc23caa4
2 fa465d8d-ead0-4d03-ae91-8a65715c7fd1 9458f0d21f98 08b3087d3dda Owner a93f1f586e03 42d81129a68e NaN 05023 Mexico 2a648576b9be NaN

3. Save encrypted mappings

The mapping dictionaries (key_maps and vault) are the sensitive artefacts — they allow de-anonymisation. Generate a Fernet key and encrypt them at rest. You can store the key to a file or an environment variable:

⚠️ Never commit your Fernet key or encrypted mappings to version control. Ensure your .gitignore includes key files and *.enc files (e.g. *.key, *.enc, data/).

import os

key = CloakDF.generate_key()
unk.save(data/"mappings.enc", key)

# Optionally store the key in an environment variable
os.environ['CLOAKDF_KEY'] = key.decode()
print("✓ Mappings saved and key stored in env var")
✓ Mappings saved and key stored in env var

4. Load and decode

Load the encrypted mappings (using the key directly or from an environment variable) and reverse the encoding:

# Load key from env var (or pass `key` directly)
loaded_key = CloakDF.load_key(env_var='CLOAKDF_KEY')
# loaded_key = CloakDF.load_key(path="path/to/keyfile")  # alternative: from file

unk2 = CloakDF.load(data/"mappings.enc", loaded_key, tables)

for name in tables:
    decoded = unk2.decode(name, encoded[name])
    pd.testing.assert_frame_equal(decoded, originals[name])
    print(f"✓ {name} round-trip OK")

decoded.head(3)
✓ customers round-trip OK
✓ orders round-trip OK
✓ employees round-trip OK
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style>
employeeID lastName firstName title titleOfCourtesy birthDate hireDate address city region postalCode country homePhone extension photo notes reportsTo photoPath
0 1 Davolio Nancy Sales Representative Ms. 1948-12-08 00:00:00.000 1992-05-01 00:00:00.000 507 - 20th Ave. E. Apt. 2A Seattle WA 98122 USA (206) 555-9857 5467 0x151C2F00020000000D000E0014002100FFFFFFFF4269... Education includes a BA in psychology from Col... 2.0 http://accweb/emmployees/davolio.bmp
1 2 Fuller Andrew Vice President, Sales Dr. 1952-02-19 00:00:00.000 1992-08-14 00:00:00.000 908 W. Capital Way Tacoma WA 98401 USA (206) 555-9482 3457 0x151C2F00020000000D000E0014002100FFFFFFFF4269... Andrew received his BTS commercial in 1974 and... NaN http://accweb/emmployees/fuller.bmp
2 3 Leverling Janet Sales Representative Ms. 1963-08-30 00:00:00.000 1992-04-01 00:00:00.000 722 Moss Bay Blvd. Kirkland WA 98033 USA (206) 555-3412 3355 0x151C2F00020000000D000E0014002100FFFFFFFF4269... Janet has a BS degree in chemistry from Boston... 2.0 http://accweb/emmployees/leverling.bmp

5. Compare encoded vs decoded

Here’s what the employees table looks like — encoded (pseudonymised) vs decoded (original):

cols = ['employeeID', 'firstName', 'lastName', 'title', 'address', 'homePhone']
display(encoded['employees'][cols].head(3))
display(decoded[cols].head(3))

Text Redaction with cloakdf.redact

While CloakDF handles reversible pseudonymisation of structured tabular data, redact handles irreversible PII removal from free text. It’s designed for case notes, narratives, and other unstructured fields where there’s no mapping to reverse, you just need the sensitive information gone.

It combines a distilbert NER model for detecting person names with regex patterns for structured identifiers (emails, phone numbers, NHS numbers, UK postcodes, and standalone numbers). Each detected entity is replaced with a flat typed placeholder like [NAME], [EMAIL], [PHONE], etc., so downstream consumers can still see what kind of information was there without seeing the actual values.

The default model is Davlan/distilbert-base-multilingual-cased-ner-hrl, a lightweight multilingual NER model. In practice it captures around 98% of PII in typical case-note text, but it is not perfect unusual name formats, ambiguous identifiers, and non-English text can produce false negatives, while common words occasionally get flagged as false positives. Always review redacted output before sharing.

from cloakdf.redact import Redactor

r = Redactor()
sample = "Dr Jane Smith called from jane.smith@nhs.net on 07123 456789. NHS: 123 456 7890, postcode SW1A 1AA."
r.redact(sample)
Loading weights:   0%|          | 0/102 [00:00<?, ?it/s]Loading weights: 100%|##########| 102/102 [00:00<00:00, 5849.88it/s]
Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.

'Dr [NAME] called from [EMAIL] on [PHONE]. NHS: [NHS], postcode [POSTCODE].'

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cloakdf-0.0.4.tar.gz (12.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cloakdf-0.0.4-py3-none-any.whl (13.0 kB view details)

Uploaded Python 3

File details

Details for the file cloakdf-0.0.4.tar.gz.

File metadata

  • Download URL: cloakdf-0.0.4.tar.gz
  • Upload date:
  • Size: 12.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for cloakdf-0.0.4.tar.gz
Algorithm Hash digest
SHA256 57dc29f2207caeefece68085b9641b9b0a67d471baaa06709b2c0da2dc46c7cf
MD5 ea79422e7c9094040871a10d680b201e
BLAKE2b-256 e091abdf850362803396d75558239e12e29ddffdbdf523176eade002930245b6

See more details on using hashes here.

File details

Details for the file cloakdf-0.0.4-py3-none-any.whl.

File metadata

  • Download URL: cloakdf-0.0.4-py3-none-any.whl
  • Upload date:
  • Size: 13.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for cloakdf-0.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 4ab11dd2fb95f92dd17ea2e2d492f0d2f2f0a2182085ee079e5808999a5275e9
MD5 8985cba25e21d25f3602dbacbf92eabc
BLAKE2b-256 b275bb1519ebaf9dfa1a5c05ab397119c4a0bca690e58e23eaf0bc144e2bf2ea

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page