C'è una domanda che faccio spesso quando qualcuno mi dice che il suo prodotto "gestisce la sicurezza dei dati": la cifratura è un'opzione che si attiva, o è il modo in cui il dato viene scritto la prima volta? Sono due cose molto diverse, anche se dall'esterno sembrano la stessa frase nel commerciale.
In DynamicShare abbiamo scelto la seconda strada per i campi sensibili — nome, email, telefono, tutto quello che passa da un form di contatto, da una registrazione, da un ordine eshop. Il payload arriva al controller, e prima ancora che qualcuno pensi a salvarlo su crm_lead o su qualunque altra tabella, passa da AesGcmCrypto, la nostra classe che applica AES-256-GCM. Non c'è un flag "cripta questo campo" da ricordarsi di spuntare, non c'è un job notturno che ripassa sui dati in chiaro. Il valore che arriva al database è già cifrato, sempre.
Il problema ovvio, a questo punto, è: se è tutto cifrato, come faccio a cercare un lead per email senza decifrare l'intera tabella ogni volta? La risposta è banale una volta che la conosci, ed è il motivo per cui questo articolo esiste: accanto al valore cifrato salviamo anche un hash SHA-256 dello stesso valore, calcolato prima della cifratura. L'hash non si può invertire, quindi non espone nulla, ma è deterministico: la stessa email produce sempre lo stesso hash. Cercare un lead per email significa quindi calcolare l'hash della email in ingresso e confrontarlo con email_hash, con un indice sopra che rende la query velocissima. Il dato in chiaro non lo vede nessuno, nemmeno il database, ma la ricerca funziona come se fosse una colonna normale.
La chiave di cifratura, MASTER_KEY, vive solo nel .env dell'API — non nel frontend, non nel pannello di amministrazione, non in nessun repository. E qui arriva la parte meno raccontabile nei materiali commerciali ma più utile da sapere davvero: la chiave usata per calcolare l'hash di ricerca, SEARCH_KEY, deve essere identica su tutti gli ambienti che fanno ricerche su quei dati. Un giorno non lo era — un disallineamento tra due .env durante un deploy — e per un paio d'ore le ricerche per email hanno smesso di trovare lead che esistevano benissimo nel database, solo con l'hash "sbagliato" rispetto a quello appena calcolato. Non un data breach, non una perdita di dati: solo hash che non combaciavano più. Ma è stato un promemoria molto concreto che la cifratura di default sposta la complessità da "ricordarsi di cifrare" a "tenere le chiavi sincronizzate", e che quella seconda cosa va trattata con la stessa cura infrastrutturale di un certificato TLS, non come una riga di configurazione qualsiasi.
L'ultimo tassello è l'isolamento: ogni azienda cliente ha il proprio database, non uno schema condiviso con una colonna tenant_id a fare da confine. È una scelta più costosa da mantenere — significa migrazioni ripetute invece che una sola — ma vuol dire anche che una query scritta male in un progetto non può, per costruzione, restituire una riga di un altro cliente. Non è la soluzione più elegante sulla carta. È quella che ci fa dormire meglio.