import hashlib
def anonimizza_cf(codice_fiscale, salt):
= codice_fiscale + salt
dato = hashlib.sha256(dato.encode())
hash_object return hash_object.hexdigest()
Codici fiscali, GDPR & Python
come anonimizzare un codice fiscale
1 obiettivi dello studio
- anonimizzazione di dati
- gestione di immagini in formato base64
- inserire immagini in base64
si può trasformare il codice fiscale in una forma che non sia immediatamente identificabile da altri, ma decifrabile solo dal titolare (ad esempio, attraverso una chiave o un algoritmo noto…)
Con la pseudonimizzazione a chiave privata, attraverso una di queste soluzioni
- Hashing con Salt Privato
- Cifratura Simmetrica
- Sostituzione Parziale
- Tokenizzazione
si può trasformare il codice fiscale in una forma che non è immediatamente identificabile da altri, ma che può essere decifrata dal titolare (ad esempio, attraverso una chiave o un algoritmo noto solo a lui).
2 Hashing con Salt Privato
L’hashing crea una stringa univoca dal codice fiscale utilizzando un algoritmo crittografico (es. SHA-256). Il salt privato (una stringa univoca aggiunta al dato originale) rende l’hash interpretabile solo dal titolare.
Procedura:
- Aggiungo un salt univoco noto solo al titolare (es. data di nascita o un valore condiviso).
- Applico l’algoritmo di hashing.
2.1 Esempio
- codice_fiscale = “RSSMRA80A01H501U”
- salt = “01/01/1980” # Salt privato conosciuto dal titolare
- hash_cf = anonimizza_cf(codice_fiscale, salt)
- Pro: Non è possibile risalire al codice fiscale senza il salt.
- Contro: Il titolare deve conoscere o avere accesso al salt.
3 Cifratura Simmetrica
La cifratura simmetrica utilizza un algoritmo come AES per codificare il codice fiscale. Il titolare può decifrare il codice con una chiave segreta condivisa.
Procedura:
- Scelgo un algoritmo di cifratura (es. AES).
- Uso una chiave segreta condivisa (es. una password).
- Cripto il codice fiscale
- Fornisco il dato criptato al titolare.
- Pro: Garantisce riservatezza elevata; solo il titolare con la chiave può decifrare.
- Contro: Il titolare deve conservare la chiave.
from cryptography.fernet import Fernet
# Genera una chiave
= Fernet.generate_key()
key
# Crea un oggetto Fernet con la chiave generata
= Fernet(key)
cipher
# Codice fiscale da cifrare
= "RSSMRA80A01H501U"
codice_fiscale
# Cifra il codice fiscale
= cipher.encrypt(codice_fiscale.encode())
encrypted_cf print("Codice cifrato:", encrypted_cf)
# Decifra il codice fiscale
= cipher.decrypt(encrypted_cf).decode()
decrypted_cf print("Codice decifrato:", decrypted_cf)
Codice cifrato: b'gAAAAABnrCyDU2Qksz7-wmAr4CXD9Zf1t9cfFphwWiz6fjP0W5-FSlPoDu9xTEkzqOKi8wRYRlRtTmqW_R2X40BvzJCCn9L3EfB5gwxmh7DhrHvnefqcrmw='
Codice decifrato: RSSMRA80A01H501U
3.1 Risultato
Eseguendo il codice, otterrai l’output:
Codice cifrato: una stringa cifrata in base64 (non leggibile direttamente).
Codice decifrato: il valore originale del codice fiscale (RSSMRA80A01H501U).
Note importanti:
Gestione della chiave: La chiave generata (key) deve essere conservata in modo sicuro se hai bisogno di decifrare i dati in futuro.
Sicurezza: Non condividere mai la chiave pubblicamente; è fondamentale per mantenere i dati al sicuro.
4 Sostituzione Parziale
Sostituisco parte del codice fiscale con caratteri generici, mantenendo solo alcune informazioni identificative per il titolare.
4.1 Esempio:
- Codice fiscale: RSSMRA80A01H501U
- Anonimizzato: RM80__**U
- Interpretazione: Mantieni iniziali e anno di nascita per rendere il codice interpretabile dal titolare.
- Pro: Facile da implementare, leggibile dal titolare.
- Contro: Non è completamente sicuro contro reidentificazione.
def anonimizza_cf_parziale(codice_fiscale):
"""
Funzione per anonimizzare parzialmente un codice fiscale.
Mantiene visibili solo i primi 3 caratteri, i caratteri 6 e 7,
e gli ultimi 4 caratteri.
"""
return f"{codice_fiscale[:3]}***{codice_fiscale[6:8]}******{codice_fiscale[-4:]}"
# Codice fiscale da anonimizzare
= "RSSMRA80A01H501U"
codice_fiscale
# Anonimizzazione parziale
= anonimizza_cf_parziale(codice_fiscale)
cf_anonimizzato print("Codice Fiscale Anonimizzato:", cf_anonimizzato)
Codice Fiscale Anonimizzato: RSS***80******501U
Spiegazione:
codice_fiscale[:3]: Prende i primi 3 caratteri.
codice_fiscale[6:8]: Prende i caratteri dalla posizione 6 alla posizione 7 inclusa.
codice_fiscale[-4:]: Prende gli ultimi 4 caratteri.
I caratteri mascherati sono sostituiti da *.
5 Tokenizzazione
Sostituisco il codice fiscale con un identificatore univoco casuale (token). Solo il titolare o un sistema può ricondurre il token al codice originale.
5.1 Esempio:
- Codice fiscale: RSSMRA80A01H501U
- Token: ABC12345XYZ
- Tabella di mapping (conservata separatamente): ABC12345XYZ -> RSSMRA80A01H501U
- Pro: Nessun dato sensibile è esposto.
- Contro: Richiede un sistema per mantenere il mapping.
import uuid
def anonimizza_cf_token(codice_fiscale):
"""
Funzione per anonimizzare un codice fiscale utilizzando un token UUID.
"""
# Genera un token unico (primi 8 caratteri di un UUID)
= str(uuid.uuid4())[:8]
token
# Crea un mapping tra il token e il codice fiscale
= {token: codice_fiscale}
mapping print("Mapping (da salvare):", mapping)
# Ritorna il token
return token
# Codice fiscale da anonimizzare
= "RSSMRA80A01H501U"
codice_fiscale
# Anonimizzazione con token
= anonimizza_cf_token(codice_fiscale)
cf_anonimizzato print("Codice Fiscale Anonimizzato:", cf_anonimizzato)
Mapping (da salvare): {'b7d6f13f': 'RSSMRA80A01H501U'}
Codice Fiscale Anonimizzato: b7d6f13f
5.2 Dettagli:
UUID Token: La funzione genera un identificatore unico (uuid.uuid4) e ne utilizza i primi 8 caratteri come token per rappresentare il codice fiscale.
Mapping: Salva il mapping tra il token e il codice fiscale originale. Questo mapping deve essere conservato in modo sicuro (es. database) per consentire una futura deanonimizzazione.
Ritorno: Il token viene restituito come codice anonimo.
Nota Importante
Il mapping è temporaneo nel codice fornito. Per un'applicazione reale, è essenziale salvare il mapping in un archivio persistente (come un database o un file) per potervi accedere successivamente.
Sicurezza: Considera l'accesso controllato al mapping per prevenire abusi e garantire la privacy degli utenti.
5.3 Quale tecnica scegliere per pubblicare un elenco di codici fiscali??
Dipende dal contesto:
- Per alta sicurezza: Cifratura simmetrica o hashing con salt.
- Per facilità d’uso: Sostituzione parziale.
- Per sistemi centralizzati: Tokenizzazione.
Se il codice fiscale deve essere stampato per il titolare (ad esempio in un report o un documento) occorre considerare contemporaneamente:
- l’interpretabilità per il titolare
- la riservatezza
La Sostituzione Parziale mantiene solo alcune parti identificative del codice fiscale, come le iniziali e un suffisso significativo. Questo metodo è semplice e leggibile (anche dagli altri, però).
RSS80*__501U
Il codice fiscale Anonimizzato con Hashing e Salt Privato utilizza l’hashing per creare un codice unico che il titolare può interpretare solo conoscendo il salt.
- codice_fiscale = “RSSMRA80A01H501U”
- salt = “01/01/1980” # Il salt può essere condiviso solo con il titolare
- Codice Fiscale Anonimizzato: 9F4E8D12
La Cifratura Simmetrica (Stampabile e Decifrabile Solo dal Titolare) produce un codice stampabile e leggibile, decifrabile solo con la chiave privata.
- codice_fiscale = “RSSMRA80A01H501U”
- Codice Fiscale Anonimizzato: gAAAAABlcO9Z8H
- Il titolare può decifrare l’intero codice fiscale utilizzando la chiave segreta.
La Tokenizzazione genera un token casuale per anonimizzare il codice fiscale, con un mapping separato conservato nel sistema.
- codice_fiscale = “RSSMRA80A01H501U”
- cf_anonimizzato = anonimizza_cf_token(codice_fiscale)
- print(“Codice Fiscale Anonimizzato:”, cf_anonimizzato)
Output:
- Mapping (da salvare): {‘1a2b3c4d’: ‘RSSMRA80A01H501U’}
- Codice Fiscale Anonimizzato: 1a2b3c4d
5.4 Conclusione
- Leggibile direttamente dal titolare: sostituzione parziale o tokenizzazione.
- Maggiore sicurezza: hashing con salt o cifratura simmetrica.
5.5 i codici fiscali delle imprese sono dati sensibili?
Il codice fiscale di un’impresa è considerato un dato personale, ma non rientra nella categoria dei dati sensibili.
Secondo il Regolamento Generale sulla Protezione dei Dati (GDPR), i dati personali sono informazioni che identificano o rendono identificabile una persona fisica, come il nome, l’indirizzo o il codice fiscale.
I dati sensibili, invece, includono informazioni che rivelano l’origine razziale o etnica, le opinioni politiche, le convinzioni religiose o filosofiche, l’appartenenza sindacale, dati genetici, dati biometrici, dati relativi alla salute o alla vita sessuale o all’orientamento sessuale di una persona (Commissione Europea)
È importante notare che il codice fiscale di una ditta individuale, essendo associato a una persona giuridica, non è soggetto alle stesse tutele previste per i dati personali delle persone fisiche.
Tuttavia, l’utilizzo del codice fiscale deve avvenire nel rispetto delle normative vigenti in materia di protezione dei dati.
5.6 Come è composto il codice fiscale
Il codice fiscale italiano è composto da 16 caratteri alfanumerici, che rappresentano una combinazione di lettere e numeri, e viene calcolato sulla base dei dati anagrafici del soggetto. La sua struttura è la seguente:
5.7 1. Cognome (3 lettere)
- Le prime tre lettere sono prese dal cognome:
- Vengono usate le prime tre consonanti.
- Se il cognome ha meno di tre consonanti, si usano le vocali per arrivare a tre lettere.
- Se il cognome ha meno di tre lettere, si aggiunge la lettera “X”.
5.8 2. Nome (3 lettere)
- Le successive tre lettere derivano dal nome:
- Si prendono le prime tre consonanti.
- Se il nome ha più di tre consonanti, si prendono la prima, la terza e la quarta.
- Se il nome ha meno di tre consonanti, si completano con le vocali.
- Anche in questo caso, se il nome ha meno di tre lettere, si aggiunge la “X”.
5.9 3. Data di nascita e sesso (6 caratteri)
- Anno di nascita (2 cifre): le ultime due cifre dell’anno (es. 1990 → 90).
- Mese di nascita (1 lettera): ogni mese è rappresentato da una lettera:
- Gennaio: A, Febbraio: B, Marzo: C, Aprile: D, Maggio: E, Giugno: H, Luglio: L, Agosto: M, Settembre: P, Ottobre: R, Novembre: S, Dicembre: T.
- Giorno di nascita e sesso (2 cifre):
- Per gli uomini si usa il giorno di nascita (es. 05 → 05).
- Per le donne, al giorno si aggiunge 40 (es. 05 → 45).
5.10 4. Comune o Stato estero di nascita (4 caratteri)
- È rappresentato da un codice numerico o alfanumerico, assegnato a ogni comune italiano o stato estero.
5.11 5. Carattere di controllo (1 carattere)
- L’ultimo carattere è un carattere di controllo, calcolato secondo un algoritmo basato su tutti i caratteri precedenti. Serve a verificare la correttezza del codice fiscale.
5.12 Esempio
Una persona nata il 15 marzo 1990 a Roma, di sesso femminile, con cognome “Rossi” e nome “Maria”:
- Cognome: ROS → ROS
- Nome: MAR → MAR
- Anno di nascita: 90
- Mese: C (marzo)
- Giorno e sesso: 55 (15 + 40)
- Comune: H501 (Roma)
- Codice di controllo: calcolato come “Z” (esempio).
Codice fiscale: ROSMAR90C55H501Z
5.13 La pseudoanomizzazione di R**M********501Z
Il punto 4 è una parte “delicata”: i codici dei comuni sono univoci; non sono univoci se si rimuove la lettera iniziale e si considerano solo le cifre.
I codici dei comuni italiani sono composti da una lettera (che identifica la provincia) seguita da un numero a tre cifre (che identifica il comune all’interno della provincia).
Il codice fiscale contiene il codice catastale (noto anche come codice Belfiore) del luogo di nascita
La combinazione completa (es. “H501” per Roma) è univoca, ma se si considera solo la parte numerica (es. “501”), ci potrebbero essere più comuni con lo stesso numero in province diverse:
- Roma ha il codice H501.
- Torino ha il codice L501.
Se togliamo la lettera, rimarrebbe solo 501, che non è più sufficiente per distinguere i comuni.
5.14 L’hash di una parola è sempre lo stesso?
Se una parola non l’abbiamo mai dichiarata in chiaro, l’hash soprebbe essere inattaccabile. Ma se da qualche altra parte la parola è stata pubblicata, abbiamo un problema. Mi spiego: “df37f9bd1129720eb09c8fb2c70201c58612e5546cba99235cba4d6af8072b00” è l’hash SHA-256 univoco di “roSSi” e differisce da tutte le altre eventuali combinazioni:
Parola | SHA-256 |
---|---|
ROSSI | c3ae8397a9b549bcb3f59dac761585227ee3ee6320e4719957a844e35a4ff50e |
rossi | 30cc6dd8ef8458e679e13ae3bf3f634cace9810e2eea03318b16e011385f93b8 |
RosSi | 92272e94fa44b8a6eb53d63528b75bf6e7d43f30e2143b2f6523f3c2f94e2b6f |
roSSi | df37f9bd1129720eb09c8fb2c70201c58612e5546cba99609769e57c03d09a58 |
ROSSi | 7e88d9c1e14ea00a4e760122d890854d7a7cfd8c95cb3dd85d48d5c9c2536eaa |
Se non altero la parola iniziale ad es con maiuscole/minuscole, punteggiatua iniziale/finale, numeri al posto delle lettere, avrò sempre lo stesso hash - L’hash di una parola è sempre lo stesso se uso lo stesso algoritmo e la stessa parola in input - Se cambio anche solo un carattere (anche la maiuscola/minuscola), l’hash cambia completamente
Se applico una funzione di hash all’intero record (ad esempio "ROSSI|100|"
e "ROSSI|200|"
), perdo però la possibilità di fare analisi esplorativa dei dati #EDA sulla prima colonna. Questo perché l’hash trasforma tutto il record in un valore unico e non permette di ricostruire le singole parti.
5.14.1 Soluzioni possibili
- Hash solo della seconda colonna
Mantengola prima colonna in chiaro per l’EDA e applico l’hash solo alle altre colonne
Esempio:
| Nome | Valore Hashato | |-------|--------------------------------------------------| | ROSSI | dffb7c5d43b1a469f7d1a93c3d4e72af68d83a5b3f401cd7 | | ROSSI | a2ef4857c9d2c8b67e2f3a5dbf2b6c1d4e8a9e7c3b01f9a2 |
- Tokenizzazione delle colonne separata
Hash ogni colonna separatamente e poi unisci gli hash.
Ad esempio, invece di hashare
"ROSSI|100|"
, faccio= hashlib.sha256("ROSSI".encode()).hexdigest() hash1 = hashlib.sha256("100".encode()).hexdigest() hash2 = f"{hash1}|{hash2}" record_hash
Così posso ancora analizzare la distribuzione dei nomi.
- Usare una funzione hash reversibile per la prima colonna
- Se la privacy non è un problema critico, potrei usare una funzione di codifica invece di un hash per la prima colonna (come Base64 o un ID numerico anonimo).
- Hash parziale
- Se voglio anonimizzare ma mantenere qualche analisi, posso troncare l’hash (ad esempio, usare solo i primi 8 caratteri).
Se l’obiettivo è proteggere i dati mantenendo l’analizzabilità, la scelta migliore è hashare solo le colonne sensibili e lasciare le chiavi utili per l’EDA in chiaro.
Es: una tabella con 10 colonne, ma solo 5 contengono dati sensibili
La soluzione migliore per mantenere la possibilità di fare #EDA è hashare solo le colonne critiche lasciando in chiaro quelle che servono per l’analisi.
5.14.3 Esempio di implementazione in Python
import pandas as pd
import hashlib
# Simuliamo un dataset con 10 colonne, di cui 5 sensibili
= pd.DataFrame({
df "ID": [1, 2, 3],
"Nome": ["Mario Rossi", "Luca Bianchi", "Giulia Verdi"],
"Codice Fiscale": ["RSSMRA80A01H501Z", "BNCLCU85B12F205X", "VRDGLI90C20H501Z"],
"Email": ["mario@mail.com", "luca@mail.com", "giulia@mail.com"],
"Telefono": ["3331234567", "3209876543", "3921112233"],
"Indirizzo": ["Via Roma 1", "Piazza Duomo 2", "Viale Marconi 3"],
"Categoria": ["A", "B", "A"],
"Regione": ["Lombardia", "Lazio", "Toscana"],
"Prodotto": ["PC", "Tablet", "Smartphone"],
"Quantità": [10, 5, 8]
})
# Funzione per hashare solo le colonne critiche
def hash_column(value):
return hashlib.sha256(value.encode()).hexdigest() if pd.notna(value) else value
# Colonne critiche da anonimizzare
= ["Nome", "Codice Fiscale", "Email", "Telefono", "Indirizzo"]
columns_to_hash
# Applichiamo l'hashing solo alle colonne sensibili
for col in columns_to_hash:
= df[col].astype(str).apply(hash_column)
df[col]
# Mostrare il dataframe anonimo
import ace_tools as tools
="Tabella anonimizzata", dataframe=df) tools.display_dataframe_to_user(name
5.14.4 Vantaggi di questo approccio
✅ EDA è ancora possibile su Categoria, Regione, Prodotto, Quantità
✅ Non si può risalire ai dati originali senza conoscere i valori iniziali
✅ Raggruppamenti e analisi sono ancora fattibili sulle colonne in chiaro
✅ Privacy garantita perché le informazioni sensibili sono criptate
5.14.5 Se devo anche unire dati tra tabelle (Join)
Se devo fare join tra dataset e le colonne sensibili sono chiavi, uso sempre lo stesso algoritmo di hash, così l’unione sarà ancora possibile.
Ad esempio, se in due tabelle compare la colonna Codice Fiscale, hasho sempre nello stesso modo, così posso ancora unirle.
Ci sono diversi modi di fare hashing, a seconda dell’algoritmo e dell’uso specifico. Ecco una panoramica delle principali categorie e algoritmi:
6 Funzioni di hash crittografiche 🔒
Queste funzioni sono progettate per essere sicure e resistenti alle collisioni.
Algoritmo | Output (bit) | Velocità | Sicurezza |
---|---|---|---|
MD5 | 128 | Veloce 🚀 | Non sicuro ❌ (facili collisioni) |
SHA-1 | 160 | Medio ⚡ | Obsoleto ❌ (collisioni trovate) |
SHA-256 | 256 | Lento ⏳ | Sicuro ✅ |
SHA-512 | 512 | Più lento 🐌 | Molto sicuro ✅✅ |
BLAKE2 | Variabile | Veloce 🚀 | Sicuro ✅ |
SHA-3 | Variabile | Medio ⚡ | Alternativa moderna ✅ |
6.1 📌 Quando usarli?
- SHA-256 o SHA-512 per anonimizzare dati
- BLAKE2 per efficienza
- MD5/SHA-1 solo per checksum e NON per sicurezza
7 Funzioni hash per password 🔑
Queste funzioni sono progettate per essere lente e resistenti agli attacchi brute-force.
Algoritmo | Caratteristiche |
---|---|
bcrypt | Lento, con salting, usato in sicurezza |
scrypt | Più resistente a attacchi hardware |
PBKDF2 | Sicuro, ma meno efficiente rispetto a bcrypt/scrypt |
Argon2 | Il più sicuro oggi, vincitore del PHC (Password Hashing Competition) |
7.1 📌 Quando usarli?
- bcrypt/scrypt per salvare password
- Argon2 per la massima sicurezza
8 Funzioni hash per strutture dati 🗂️
Queste funzioni sono usate in database, tabelle hash e strutture dati.
Algoritmo | Usi |
---|---|
Python hash() |
Per dizionari e set, cambia a ogni esecuzione! |
MurmurHash | Veloce e ottimo per database |
CityHash / FarmHash | Ottimizzati per grandi dataset |
xxHash | Molto veloce e leggero |
8.1 📌 Quando usarli?
- Dizionari e database per indicizzazione veloce
- Bloom filters per verifiche rapide
9 Hashing non crittografico per compressione 🔄
Questi algoritmi sono usati per ridurre il numero di bit mantenendo una distribuzione uniforme.
Algoritmo | Usi |
---|---|
CRC32 | Controllo errori nei file |
Adler-32 | Veloce, ma meno sicuro |
FNV-1a | Usato in alcune tabelle hash |
9.1 📌 Quando usarli?
- Checksum su file (MD5/SHA-1 sono comunque preferibili)
- Compressione rapida senza esigenze di sicurezza
10 🛠 Quale usare?
- Per proteggere dati sensibili → SHA-256, SHA-512, BLAKE2
- Per salvare password → bcrypt, scrypt, Argon2
- Per strutture dati e database → MurmurHash, xxHash
- Per verificare integrità file → CRC32, SHA-1