Nouveau dans Symfony 8.2 (en développement). Avec #[Encrypted], une colonne est chiffrée en base, et Doctrine la déchiffre en relisant l'entité. Mais le chiffrement est aléatoire : la même adresse, chiffrée deux fois, ne donne jamais les mêmes octets. Un WHERE email = ? ne trouve donc jamais rien, et on ne peut plus retrouver un patient par son adresse. L'index aveugle (blind index) résout ce problème.
Le principe
À côté de la colonne chiffrée, on garde une empreinte à clé de la valeur, un HMAC-SHA256 : la même adresse donne toujours la même empreinte, mais il faut la clé pour la calculer. Qui lit la base voit des empreintes, sans pouvoir les rapprocher d'une adresse ; il voit en revanche quels patients partagent la même, ce que le chiffrement seul cachait. On n'indexe donc que des valeurs variées, cherchées à l'égalité : une adresse, un numéro de dossier, pas un pays ni une année de naissance. Pour chercher, on calcule l'empreinte de l'adresse demandée et on la compare à celles de la base.
Depuis Symfony 8.2
#[ORM\Column] #[Encrypted(key: 'patient.contact')] private string $email; #[ORM\Column(length: 64)] #[BlindIndexed('email', 'patient-email')] private string $emailIndex = '';
#[BlindIndexed] dit d'où vient l'empreinte : de la propriété email, par l'index nommé patient-email. Un écouteur la recalcule à chaque flush() qui écrit l'entité, à l'insertion comme à la modification. On n'a donc pas à y penser, ni à risquer une ligne dont l'empreinte n'aurait pas été écrite et qu'aucune recherche ne retrouverait.
L'index est un service, déclaré avec sa clé de données et sa projection, c'est-à-dire la forme de la valeur qu'il indexe. La projection Email normalise le domaine, seule partie de l'adresse que la norme déclare insensible à la casse : ada@Example.ORG et ada@example.org partagent alors une empreinte. La partie avant @, elle, garde sa casse : Ada@example.org est une autre adresse. Si votre application doit confondre les deux, normalisez l'adresse à son entrée, avant de l'enregistrer : ce n'est pas à l'index d'en décider.
La recherche passe par cet index, qui calcule l'empreinte de l'adresse cherchée :
$patients->findOneBy(['emailIndex' => $index->of($email)]);
L'écouteur ne voit que ce qui passe par le flush() : une ligne insérée en SQL n'a pas d'empreinte, et une ligne modifiée par un UPDATE en masse garde l'ancienne, qui désigne alors une adresse qu'elle n'a plus.
À faire
L'adresse des patients est déjà chiffrée, et config/services.yaml déclare l'index patient-email. Mais PatientRepository::trouverParEmail() cherche encore sur la colonne chiffrée, et l'aperçu /patients/recherche ne trouve donc personne. Il montre aussi, sous enBase, ce que la base contient.
- Ajoutez à
Patientla colonneemailIndex, remplie par l'indexpatient-email. - Faites chercher
trouverParEmail()par l'empreinte de l'adresse.
L'adresse doit rester chiffrée.