dapla-toolbelt-pseudo
dapla-toolbelt-pseudo er en python-pakke som har som sitt hovedformål å gi Dapla-brukere muligheten til å pseudonymisere, de-pseudonymisere og re-pseudonymisere data. Det skal sikre at brukerne av Dapla har verktøyene de trenger for å jobbe med direkte identifiserende opplysninger i henhold til lovverk og SSBs tolkninger av disse.
Siden tilgang til direkte identifiserende opplysninger er underlagt strenge regler, så krever bruken av dapla-pseudo-toolbelt at man forholder seg til vedtatte standarder som datatilstander og systemer som Kildomaten. I tillegg er det en streng tilgangsstyring til hvor man kan kalle funksjonaliteten fra. Tjenestene er satt opp på en slik måte at Dapla-team skal være selvbetjent i bruken av funksjonaliteten, samtidig som regler, prosesser og standarder etterleves på enklest mulig måte.
I SSB er det bestemt at all data skal klassifiseres på en standardisert måte basert på datatilstander for å avgjøre om de er sensitive, skjermet eller åpen. Den eneste datatilstanden som klassifiseres som sensitiv er kildedata. Det er derfor bestemt at pseudonymisering er en av prosesseringene som skal skje mellom datatilstandene kildedata og inndata.
Forberedelser
Før man tar i bruk funksjonaliteten er det viktig at man kjenner godt til tilgangstyring i Dapla-team og Kildomaten, og har diskutert med seksjonen hvordan man skal behandle direkte identifiserende opplysninger i de aktuelle dataene.
For at et Dapla-team skal kunne bruke dapla-toolbelt-pseudo må Kildomaten være skrudd på for miljøet1 man ønsker å jobbe fra. Som standard får alle statistikkteam skrudd på Kildomaten i prod-miljøet og ikke i test-miljøet. Ønsker du å aktivere Kildomaten i test-miljøet kan dette gjøres selvbetjent som en feature.
Tilgangsstyring
Tilgang til å funksjonalitet i dapla-toolbelt-pseudo kan regnes som sensitivt i seg selv, og derfor er det en streng tilgangsstyring for bruk av tjenesten. I prod-miljøet kan man kun ta i bruk funksjonaliteten ved å prosessere dataene i Kildomaten, og det er bare tilgangsgruppen data-admins som har tilgang til å godkjenne slike automatiske prosesseringer. I test-miljøet derimot kan alle på teamet benytte seg av all funksjonalitet, siden det aldri skal forekomme ekte data her.
Aktør | Validator | Pseudonymize() | Depseudonymize() | Repseudonymize() |
---|---|---|---|---|
Kildomaten | ✅ | ✅ | ✅ | ✅ |
data-admins (interaktivt) | ✅ | ✅ | ✅ | ✅ |
developers (interaktivt) | ✅ | ✅ | ✅ | ✅ |
Aktør | Validator | Pseudonymize() | Depseudonymize() | Repseudonymize() |
---|---|---|---|---|
Kildomaten | ✅ | ✅ | 🚫 | 🚫 |
data-admins (interaktivt) | 🚫 | 🚫 | 🚫 | 🚫 |
developers (interaktivt) | 🚫 | 🚫 | 🚫 | 🚫 |
I Tabell 1 ser vi fra Tabell 1 (a) at man i test-miljøet har full tilgang til funksjonaliteten i dapla-toolbelt-pseudo, både fra Kildomaten og når man jobber interaktivt2 i Jupyterlab. Tabell 1 (b) viser at det kun er tilgang til pseudonymize() og validator() fra Kildomaten i prod-miljøet, og man kan aldri interaktivt kan kalle på funksjoner som potensielt avslører et pseudonym. Av den grunn er det alltid anbefalt å teste ut koden sin i test-miljøet før den produksjonssettes i i prod-miljøet med Kildomaten.
Selv om man har videre rettigheter til å bruke funksjonaliteten i dapla-toolbelt-pseudo fra test-miljøet sammenlignet med prod-miljøet, så betyr ikke det at samme input i de to miljøene vil samme output. Når funksjonaliteten kalles fra test-miljøet så benyttes det automatisk en annen krypteringsnøkkel enn den som benyttes i prod. På den måten vil et pseudonym produsert fra prod-miljøet aldri være likt det som produseres fra prod-miljøet selv om input skulle være den samme.
Funksjonalitet
I denne delen viser vi hvilken funksjonalitet som tilbys gjennom dapla-toolbelt-pseudo. Eksempelkoden under viser hvordan man ville kjørt det fra en notebook i test-miljøet til teamet, og ikke hvordan koden må skrives når det skal kjøres i Kildomaten3.
Installering
dapla-toolbelt-pseudo er ferdig installert i Kildomaten. Men ønsker du å bruke den i test-miljøet til teamet så kan du installere det i et ssb-project fra PyPI med denne kommandoen:
Terminal
poetry add dapla-toolbelt-pseudo
Dataformater
dapla-toolbelt-pseudo støtter innlesing av følgende dataformater:
- CSV
- JSON
- Pandas dataframes
- Polars dataframes
Eksemplene under viser hovedskelig innlesing av dataframes fra minnet4, men du kan lese mer om filbasert prosessering lenger ned i kapitlet.
Pseudonymisering
Pseudonymisering tilbys via Pseudonymize
-metoden i dapla-toolbelt-pseudo. Den følger et builder-pattern der man spesifiserer hva og i hvilken rekkefølge operasjonene skal gjøres. Anta at det finnes en Polars dataframe i minnet som heter df
hvor kolonnen fnr
skal pseudonymiseres. Da vil koden se slik ut:
Notebook
from dapla_pseudo import Pseudonymize
= (
result_df
Pseudonymize.from_polars(df) "fnr")
.on_fields(
.with_default_encryption()
.run()
.to_polars() )
I koden over så angir from_polars(df)
at kolonnen vi ønsker å pseudonymisere ligger i en Polars dataframe i minnet og heter df
. Deretter spesifiserer vi at kolonnen fnr
er den som skal behandles med funksjonen on_fields("fnr")
. Videre angir with_default_encryption()
at fnr
skal pseudonymiseres med dapla-toolbelt-speudo sin standard-algoritme5. Til slutt ber vi om at det ovennevnte blir kjørt med funksjonen run()
, og at dataene skal returneres som en Polars dataframe med funksjonen to_polars()
.
Se flere eksempler i dokumentasjonen.
De-pseudonymisering
De-pseudonymisering tilbys via Depseudonymize
-metoden i dapla-toolbelt-pseudo. Den følger et builder-pattern der man spesifiserer hva og i hvilken rekkefølge operasjonene skal gjøres. Anta at det finnes i en Polars dataframe i minnet som heter df
hvor kolonnen fnr
skal de-pseudonymiseres. Da vil koden se slik ut:
Notebook
= (
result_df
Depseudonymize.from_polars(df) "fnr")
.on_fields(
.with_default_encryption()
.run()
.to_polars() )
Oppbygningen av koden med Depseudonymize()
er helt lik som for Pseudonomize()
. Les beskrivelsen der for å se hva de ulike funksjonskallene gjør. Se flere eksempler i dokumentasjonen.
De-pseudonymisering er også støttet for informasjon som først er transformert til stabil ID og deretter pseudonymisert med Papis-nøkkelen. I disse tilfellene kan det også være behov for å spesifisere hvilken versjon av snr-katalogen man ønsker å benytte for å erstatte snr med fødselsnummer:
Notebook
from dapla_pseudo import Depseudonymize
= (
result_df
Depseudonymize.from_pandas(df) "fnr")
.on_fields(
.with_stable_id(="2023-05-29")
sid_snapshot_date
.run()
.to_pandas() )
I eksempelet over spesifiserer vi datoen 2023-05-29
og da benyttes snr-katalogen som ligger nærmest i tid til denne datoen. Hvis sid_snapshot_date
ikke oppgis benyttes siste tilgjengelige versjon av katalogen.
Foreløpig er det kun tilgang til å pseudonymisere i test-miljøet med test-data. Ta kontakt med Dapla-teamene dersom det dukker opp behov for å kunne de-pseudonymisere i prod-miljøet.
Re-pseudonymisering
Under utvikling.
Stabil ID
I statistikkproduksjon og forskning er det viktig å kunne følge de samme personene over tid. Derfor har fødselsnummer ofte blitt oversatt til en mer stabilt identifikator, ofte kalt SNR eller stabil ID6. Funksjonene with_stable_id()
og validator()
bruker stabilID-katalogen til å henholdsvis bytte ut fødselsnummer med stabil ID, og for å validere om fødselsnummer er gyldige(se under).
Du kan selv spesifisere hvilken versjon av SNR-katalogen du ønsker å bruke. Det gjør du ved å oppgi en gyldighetsdato og så finner dapla-toolbelt-pseudo hvilken versjon av katalogen som ligger nærmest i tid.
Papis-pseudonym
Dapla tilbyr samme pseudonym som Papis-prosjektet7. Denne kan benyttes på 2 måter:
- Pseudonymisere hvilken som helst informasjon med samme nøkkel som Papis.
- Transformere fødselsnummer til snr-nummer og deretter pseudonymisere med samme nøkkel som Papis.
Punkt 1 er nyttig for de som har pseudonymisert informasjon på bakken tidligere og vil ha samme pseudonym på Dapla8. Dette kan gjelde hvilken som helst informasjon, også direkte pseudonymisering av fødselsnummer, uten at det er gått via snr-nummer. Her er et eksempel på hvordan man pseudonymiserer på denne måten:
Notebook
= (
result
Pseudonymize.from_pandas(df)"fornavn")
.on_fields(
.with_papis_compatible_encryption()
.run()
.to_pandas() )
Punkt 2 over er nok den som benyttes mest i SSB siden den sikrer at pseudonymisert fødselsnummer kan kobles med data som er pseudonymisert på bakken. Her er et eksempel på hvordan man pseudonymiserer snr med Papis-nøkkelen:
Notebook
= (
result
Pseudonymize.from_pandas(df)"fnr")
.on_fields(
.with_stable_id()
.run()
.to_pandas() )
At Papis og Dapla tilbyr samme pseudonum betyr egentlig at vi bruker samme krypteringsalgoritme, og en felles krypteringsnøkkel. Krypteringsalgoritmen som benyttes er formatpreserverende (FPE) og biblioteket som brukes er Tink FPE Python. En begrensning med algorimen er at kun karakterer som finnes i et forhåndsdefinert karaktersett (tall, store og små bokstaver fra a-z) blir vurdert. Andre karakterer f. eks æøå
blir ikke kryptert. Papis-nøkkelen (som brukes f. eks for fnr og snr) benytter en SKIP
-strategi for karakterer som faller utenom, som betyr at algoritmen simpelthen “hopper over” disse. FPE-algoritmen er også avhengig av størrelsen på det predefinterte karaktersettet for å avgjøre minimumslengden på teksten som pseudonymiseres. For Papis-nøkkelen betyr det at teksten minst må være 4 karakterer lang. Kortere tekster blir ikke kryptert.
Validere fødselsnummer
Validator
-metoden kan benyttes til å sjekke om fødselsnummer finnes i SNR-katalogen (se over), og returnerer de ugyldige fødselsnummerne tilbake. Her kan man også spesifisere hvilken versjon av SNR-katalogen man ønsker å bruke. Standard, hvis ingenting velges, er at siste tilgjengelige versjon velges. Under er et eksempel på hvordan man validerer fødselsnummer for en gitt gyldighetsdato:
Notebook
from dapla_pseudo import Validator
= (
result
Validator.from_polars(df)"fnr")
.on_field(
.validate_map_to_stable_id(="2023-08-29"
sid_snapshot_date
)
)# Vis hvilken versjon av SNR-katalogen som er benyttet
result.metadata# Vis fødselsnummer som ikke fikk treff i SNR-katalogen som en Polars dataframe
result.to_polars()
Filbasert prosessering
Selv om mange av eksemplene i dette kapitlet viser hvordan man bruker dapla-toolbelt-pseudo ved å gi funksjonene et datasett fra minnet, så støtter den også å prosessere filer som er lagret på disk/bøtter. Dette kan være en fordel hvis dataene er for store for Kildomaten (>32GB RAM) eller de har en dyp hierarkisk struktur (f.eks. json-filer).
dapla-toolbelt-pseudo støtter følgende filtyper:
- csv
- json
Her er et eksempel på hvordan man pseudonymiserer en fil som ligger lagret på disk:
Notebook
from dapla_pseudo import Pseudonymize
from dapla import AuthClient
= "gs://bucket/pseudo-examples/andeby_personer.csv"
file_path
= {
options "dtypes": {"fnr": pl.Utf8, "fornavn": pl.Utf8, "etternavn": pl.Utf8, "kjonn": pl.Categorical, "fodselsdato": pl.Utf8}
}
= (
result_df
Pseudonymize.from_file(file_path)"fnr")
.on_fields(
.with_default_encryption()
.run()**options)
.to_polars( )
Se flere eksempler i dokumentasjonen.
Wildcards
Kommer snart.
Dataminimering
Kommer snart.
Algoritmer
Kommer snart.
Metadata
Det genereres to typer av metadata når man pseudonymiserer:
- Datadoc
- Metadata
De to typene av metadata returneres til brukeren i to forskjellige objekter.
Datadoc
Datadoc-metadata er på et format som er planlagt integrert i Datadoc9 på et senere tidspunkt. I koden til høyre så printes metadataene fra et kall til Pseudonomize
ved å skrive print(result.datadoc)
. Da printer man objektet interaktivt i f.eks. Jupyterlab, noe som kun er mulig i test-miljøet. Skal man kjøre dette i Kildomaten så er det lettere å skrive filen direkte til riktig json-format med to_file
-funksjonen. Da får får filen endelsen __DOC
på slutten av filnavnet, og man slipper å tenke på om filen skrives med riktig formattering, osv..
Når man kjører pseudonymisering fra Kildomaten er det viktig å tenke på at felter som er pseudonymisert ikke må endres uten at metadataene også endrer. Da kan man risikere at metadatene ikke lenger beskriver riktig informasjon.
Under ser man hvilken informasjon som genereres fra pseudonymiseringen.
Datadoc
{
"document_version": "0.0.1",
"datadoc": null,
"pseudonymization": {
"document_version": "0.1.0",
"pseudo_dataset": null,
"pseudo_variables": [
{
"short_name": "fnr",
"data_element_path": "fnr",
"data_element_pattern": "**",
"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"
}
],
"source_variable": null,
"source_variable_datatype": null
}
]
}
}
Av metadatene kan vi se fra pseudo_variables
at det bare var feltet fnr
som ble pseudonymisert. Vi ser også av stable_identifier_type
ser vi at fnr
ble oversatt til snr
, og at versjonen av SNR-katalogen var fra 2023-08-31
. encryption_algorithm
angir at det var det var den formatpreserverende algoritmen TINK-FPE
som ble benyttet. keyID: "papis-common-key-1"
angir hvilken nøkkel-id som ble benyttet. strategy: "SKIP"
refererer til at den format-preserverende algoritmen skal “hoppe over” ugyldige karakterer og la de være som de er.
Denne informasjonen vil være svært nyttig i SSB hvis man senere skal kunne de-pseudonymisere eller re-pseudonymisere data.
Metadata
Den andre typen av metadata kan hentes ut etter et kall til Pseudonymize
med kommandoen result.metadata
. Den returnerer en python dictionary. Den inneholder hovedsaklig logginformasjon og metrikker foreløpig. For de som pseudonymiserer med with_stable_id()
kan output se slik ut:
Metadata
{
'fnr': {
'logs': [
'No SID-mapping found for fnr 999999*****',
'No SID-mapping found for fnr XX',
'No SID-mapping found for fnr X8b7k2*'
],
'metrics': [
{'MAPPED_SID': 10},
{'MISSING_SID': 3}
]
}
}
I Kildomaten kan det vært nyttig å printe denne informasjonen til loggene. Av eksempelet over ser vi at verdier som er over 6 karakterer lange blir maskert.
Brukerveiledning
På grunn av den strenge tilgangsstyringen til dapla-pseudo-toolbelt og kildedata er det anbefalt å utvikle kode for overgangen fra kildedata til inndata i test-miljøet til teamet. I denne delen viser vi hvordan denne arbeidsflyten kan se ut, fra testing til en automatisert produksjon med ekte data, med et helt konkret eksempel.
Interaktiv utvikling
For å kunne kjøre pseudonymiseringen interaktivt i f.eks. en notebook i Jupyter, så må man jobbe i test-miljøet til teamet.
Kildomaten i test
Kommer snart.
Kildomaten i prod
Kommer snart.
Fotnoter
Et Dapla-team har både et test- og et prod-miljø. Kildomaten må være skrudd på i det miljøet du ønkser å benytte dapla-toolbelt-pseudo fra.↩︎
Med interaktiv jobbing menes at man skriver og kode og får tilbake output i samme verktøy. F.eks. er Jupyterlab et eksempel på et verktøy som lar deg jobbe interaktivt med data.↩︎
I Kildomaten må koden blant annet pakke inn i
main()
-funksjon.↩︎Pandas og Polars dataframes er eksempler på dataformater som lever i minnet, og må konverteres før de skrives til et lagringsommråde. I praksis vil det ofte si at man jobber med dataframes når man jobber i verktøy som Jupyterlab, mens man skriver til lagringsområde når man er ferdig i Jupyterlab.↩︎
Standardalgoritmen i dapla-toolbelt-pseudo er den deterministiske krypteringsalgoritmen Deterministic Authenticated Encryption with Associated Data, eller DAEAD-algoritmen.↩︎
SNR-katalogen eies og tilbys av Team Register på Dapla.↩︎
Papis var et prosjekt med fokus på bakkesystemene i SSB som skulle bringe SSBs behandling av personopplysninger, som brukes i statistikkproduksjon, i samsvar med GDPR gjennom en enhetlig pseudonymiseringsløsning.↩︎
Generelt sett er det ikke å anbefale å benytte denne nøkkelen på annen informasjon enn fødselsnummer. Grunnen er at den er svakere enn andre algoritmer, der blant annet tekst som er kortere enn 4 karakter lang ikke blir pseudonymisert.↩︎
Datadoc er det nye systemet for dokumentasjon av datasett i SSB. Systemet er fortsatt under utvikling.↩︎