Pseudonymisering med testdata

Bruk testdata for å bli kjent med pseudonymisering.

Pseudonymisering
Python
dapla-toolbelt-pseudo
Forfatter
Tilhører

Øyvind Bruer-Skarsbø

Seksjon for dataplattform (724)

Opprettet

July 11, 2024

Endret

September 23, 2024

Den strenge tilgangsstyringen til pseudonymiseringsfunksjonaliteten på Dapla gjør at det er vanskelig for brukere å bli kjent med funksjonaliteten ved bruk av produksjonsdata. Derfor bør alle som jobber med dette starte med å bruke testdata og jobbe i test-miljøet på Dapla.

Importere

Først så importerer vi noen biblioteker som vi skal benytte. Du kan benytte standard-kernelen som heter python3, for der er biblioteket som pseudonymiserer, dapla-toolbelt-pseudo, tilgjengelig.

import json

import dapla as dp
import pandas as pd
from dapla_pseudo import Depseudonymize, Pseudonymize
from dapla_pseudo.constants import MapFailureStrategy
from dapla_pseudo.utils import convert_to_date
from IPython.display import JSON

Versjonen av dapla-toolbelt-pseudo er 2.1.2.

Dataene vi skal bruke syntetiske fødselsnummer fra testversjonen SNR-katalogen. På den måten får vi også testet pseudonymiseringen via SNR-katalogen som er veldig vanlig i SSB. Denne SNR-katalogen ligger som en fil i en bøtte som alle i SSB har tilgang til.

path = "gs://ssb-dapla-felles-data-produkt-test/freg/snr_kat.csv"

df = dp.read_pandas(path, file_format="csv", dtype={"fnr": str, "fnr_date": str})

df.head(n=5).style.set_table_styles([
    {'selector': 'th', 'props': [('text-align', 'left')]}
])
Tabell 1: Syntetisk versjon av SNR-katalogen
  fnr current_fnr snr current_snr fnr_date current_fnr_date
0 16890249063 16890249063 026mxd3 026mxd3 20201222 20201222
1 15854996565 15854996565 34qm7pt 34qm7pt 20201222 20201222
2 27871547810 27871547810 53uxelp 53uxelp 20201222 20201222
3 50889200399 50889200399 f35lbnf f35lbnf 20201222 20201222
4 22919199052 22919199052 c2hxvdv c2hxvdv 20201222 20201222

Fra Tabell 1 ser vi at datasettet inkluderer en del kolonner. For utforsking av pseudonymiseringsfunksjonalitet så trenger vi kun fnr-kolonnen.

Forberedelse av datasettet

La oss kun beholde fnr-kolonnen og kopiere den en ny kolonne slik at vi enklere kan sammenligne før og etter pseudonymisering. I tillegg kutter jeg antall rader til 10, siden vi ikke trenger noe mer for formålet her.

df2 = df.head(n=10)
df3 = df2[["fnr"]]
df4 = df3.copy()
df4['fnr_original'] = df4['fnr']

df4.head(n=5).style.set_table_styles([
    {'selector': 'th', 'props': [('text-align', 'left')]}
])
Tabell 2: Nedstrippet versjon av SNR-katalogen
  fnr fnr_original
0 16890249063 16890249063
1 15854996565 15854996565
2 27871547810 27871547810
3 50889200399 50889200399
4 22919199052 22919199052

Hvis du ønsker å teste hvordan krypteringsalgoritmene fungerer med kolonner som inneholder navn, så kan vi generere noe data med et også.

fornavn = [
    "Jo",
    "Hans-August",
    "Nils",
    "Eva",
    "Lars",
    "Øyvind",
    "Kenneth",
    "Johnny",
    "Rupinder",
    "Nicolas",
]
etternavn = [
    "Nordman",
    "Karlsen",
    "Nordmann",
    "Karlsen",
    "Carlsen",
    "Nordmann",
    "Carlsen",
    "Nordmann",
    "Karlsen",
    "Normann",
]

df4['fornavn'] = fornavn
df4['etternavn'] = etternavn
df5 = df4.copy()

Til slutt legger vi på noen ugyldige fødselsnummer slik at vi får testet hvordan algoritmene håndterer dette.

new_row1 = pd.DataFrame(
    [
        {
            "fnr": "99999999999",
            "fnr_original": "99999999999",
            "fornavn": "Michael",
            "etternavn": "Norman",
        }
    ]
)
df6 = pd.concat([df5, new_row1], ignore_index=True)

new_row2 = pd.DataFrame(
    [{"fnr": "XX", "fnr_original": "XX", "fornavn": "Ola Glenn", "etternavn": "Gåås"}]
)
df7 = pd.concat([df6, new_row2], ignore_index=True)

new_row3 = pd.DataFrame(
    [
        {
            "fnr": "X8b7k28",
            "fnr_original": "X8b7k28",
            "fornavn": "Lars",
            "etternavn": "Gaas",
        }
    ]
)
df8 = pd.concat([df7, new_row3], ignore_index=True)

df8.head(n=5).style.set_table_styles([
    {'selector': 'th', 'props': [('text-align', 'left')]}
])
Tabell 3: Datasett for å teste pseudonymiseringsfunksjonalitet
  fnr fnr_original fornavn etternavn
0 16890249063 16890249063 Jo Nordman
1 15854996565 15854996565 Hans-August Karlsen
2 27871547810 27871547810 Nils Nordmann
3 50889200399 50889200399 Eva Karlsen
4 22919199052 22919199052 Lars Carlsen

Tabell 3 viser datasettet vi skal bruke til å teste med.

Pseudonymisering

Nå kan vi begynne å leke med dataene. Det første vi kan gjøre er å pseudonymisere med den mest vanlige algoritmen som benyttes i produksjon: Papis-nøkkelen.

result = (
    Pseudonymize.from_pandas(df8)
    .on_fields("fnr")
    .with_stable_id()
    .run()
)
result.to_pandas()
Unexpected length of metadata: 2
Tabell 4: Pseudonymiserer med Papis-algoritmen
fnr fnr_original fornavn etternavn
0 BnQe23u 16890249063 Jo Nordman
1 I1mQmBP 15854996565 Hans-August Karlsen
2 eVbGYLy 27871547810 Nils Nordmann
3 O4jegSM 50889200399 Eva Karlsen
4 6JhhRKi 22919199052 Lars Carlsen
5 vygqpLq 66821775168 Øyvind Nordmann
6 JWzitL8 13824498614 Kenneth Carlsen
7 oIhVngD 46927100797 Johnny Nordmann
8 0y8qqUZ 16907699157 Rupinder Karlsen
9 FNnp5AL 10920998203 Nicolas Normann
10 4yI2BlkviaI 99999999999 Michael Norman
11 XX XX Ola Glenn Gåås
12 tKHXmUl X8b7k28 Lars Gaas

I Tabell 4 ser vi at kolonnen fnr har blitt pseudonymisert. Det er også verdt å legge merke til at kolonnen ikke endrer navn. Grunnen til at lengden på verdiene som er pseudonymiserte er på 7 tegn for de opprinnelige fødselsnummerne, er at det først skjer en oversetting fra fnr til snr før det pseudonymiseres, og snr-nummerserien er på 7 tegn. Med andre ord så preserverer algoritmen lengden på snr-nummeret siden det er dette som pseudonymiseres.

Det er også verdt å merke seg at verdier som er kortere enn 4 i lengde, f.eks. XX i rad 11, ikke blir pseudonymisert i det hele tatt. Verdier som er 4 eller lengre, vil bli pseudonymisert selv om de ikke fikk treff i SNR-katalogen.

Metadata

Det genereres også 2 metadata-objekter ved pseudonymisering. Disse er:

  1. result.datadoc
  2. result.metadata

La oss se nærmere på de:

data = json.loads(result.datadoc)
display(data)
{'document_version': '0.0.1',
 'pseudonymization': {'document_version': '0.1.0',
  'pseudo_variables': [{'short_name': 'fnr',
    'data_element_path': 'fnr',
    'data_element_pattern': '/fnr',
    'stable_identifier_type': 'FREG_SNR',
    'stable_identifier_version': '2023-08-31',
    'encryption_algorithm': 'TINK-FPE',
    'encryption_key_reference': 'papis-common-key-1',
    'encryption_algorithm_parameters': [{'keyId': 'papis-common-key-1'},
     {'strategy': 'skip'}]}]}}

Dette er metadata som skal integreres i Datadoc etter hvert.

La oss se på den andre typen metadata:

display(result.metadata)
{'logs': ['No SID-mapping found for fnr 999********',
  'No SID-mapping found for fnr X8b****'],
 'metrics': {'MAPPED_SID': 10, 'FPE_LIMITATION': 1, 'MISSING_SID': 2}}

Her ser vo at 10 felt fikk treff i SNR-katalogen, 1 felt var for kort for algoritmen, og 2 felt fikk ikke treff SNR-katalogen. Vi får også se litt fødselsnummeret til de 2 som ikke fikk treff.

Depseudonymisering

La oss ta vare på den pseudonymiserte kolonnen og så depseudonymisere og se om resultatet blir riktig:

result2 = result.to_pandas()
result2['pseudo_fnr'] = result2['fnr']

result_df = (
    Depseudonymize.from_pandas(result2)         
    .on_fields("fnr")                              
    .with_stable_id()                              
    .run()                                         
    .to_pandas() 
)

result_df.style.set_table_styles([
    {'selector': 'th', 'props': [('text-align', 'left')]}
])
Tabell 5: Depseudonymisering av fødselsnummer
  fnr fnr_original fornavn etternavn pseudo_fnr
0 16890249063 16890249063 Jo Nordman BnQe23u
1 15854996565 15854996565 Hans-August Karlsen I1mQmBP
2 27871547810 27871547810 Nils Nordmann eVbGYLy
3 50889200399 50889200399 Eva Karlsen O4jegSM
4 22919199052 22919199052 Lars Carlsen 6JhhRKi
5 66821775168 66821775168 Øyvind Nordmann vygqpLq
6 13824498614 13824498614 Kenneth Carlsen JWzitL8
7 46927100797 46927100797 Johnny Nordmann oIhVngD
8 16907699157 16907699157 Rupinder Karlsen 0y8qqUZ
9 10920998203 10920998203 Nicolas Normann FNnp5AL
10 99999999999 99999999999 Michael Norman 4yI2BlkviaI
11 XX XX Ola Glenn Gåås XX
12 X8b7k28 X8b7k28 Lars Gaas tKHXmUl

Tabell 5 viser at depseudonymiseringen returnerer de opprinnelige fødselsnummerne.

Videre kan man utforske å pseudonymisere navn ved bruk av ulike algoritmer.