Dans l’ère numérique actuelle, où la sécurité des données occupe une place centrale, la pseudonymisation émerge comme une stratégie cruciale pour renforcer la confidentialité des informations stockées dans les bases de données. Cette approche, bien que semblable à l’anonymisation, se distingue par son objectif spécifique de préserver l’utilité des données tout en masquant l’identité réelle des individus. Dans le contexte de PostgreSQL, la pseudonymisation offre un équilibre délicat entre protection des renseignements sensibles et préservation de la fonctionnalité des données.
Principe de la Pseudonymisation :
La pseudonymisation implique la substitution des données réelles par des données fictives, mais conservant leur structure originale. Contrairement à l’anonymisation, qui supprime complètement toute référence à l’identité d’un individu, la pseudonymisation permet la réversibilité du processus. Ainsi, les données restent utilisables à des fins légitimes : l’analyse statistique, ou la réalisation de tests, tout en garantissant la protection des informations confidentielles.
L’utilité fondamentale de la pseudonymisation réside dans sa capacité à concilier deux impératifs apparemment contradictoires : la protection de la vie privée des individus et la nécessité d’accéder et de traiter des données. Dans un paysage où les fuites de données et les violations de la vie privée sont de plus en plus fréquentes, la pseudonymisation devient une réponse pragmatique aux exigences de conformité réglementaire tout en préservant la valeur analytique des données.
Souvent confondue avec la pseudonymisation, l’anonymisation diffère par son caractère irréversible. Alors que l’anonymisation supprime toute possibilité de relier des données à une identité spécifique, la pseudonymisation offre une réversibilité calculée, permettant une utilisation future des données tout en maintenant un niveau élevé de sécurité. Cette distinction cruciale souligne l’importance de choisir la méthode la plus appropriée en fonction des besoins spécifiques de sécurité et des objectifs opérationnels.
Etat de la pseudonymisation actuellement sur PostgreSQL :
Le seul outil actuellement disponible sur le marché permettant de réaliser une pseudonymisation sur PostgreSQL est une extension. Nous l’avions déjà évoquée lors d’un précédent article : PostgreSQL Anonymizer. Pour rappel, cette extension est développée par Dalibo, et contient également de nombreuses options d’anonymisation.
Dans cet article, nous allons rappeler l’installation de cette extension puis nous la verrons à l’œuvre dans quelques exemples de pseudonymisation de données.
Etape 1 : Installation
La machine choisie pour mon test est une Ubuntu. Il n’y a pas de package prêt à l’emploi sur ce système d’exploitation. Nous devons donc réaliser nous même la compilation de l’extension pour qu’elle puisse fonctionner. Nous avons au préalable installé une version 15 de PostgreSQL sur notre machine.
Nous commençons par installer les outils de développement de PostgreSQL :
root@sarah:~# sudo apt-get install postgresql-server-dev-15 Reading package lists... Done Building dependency tree Reading state information... Done ... Setting up postgresql-server-dev-15 (13.7-1.pgdg18.04+1) ... Processing triggers for man-db (2.8.3-2ubuntu0.1) ... Processing triggers for mime-support (3.60ubuntu1) ... Processing triggers for ureadahead (0.100.0-21) ... Processing triggers for install-info (6.5.0.dfsg.1-2) ... Processing triggers for libc-bin (2.27-3ubuntu1.5) ... Processing triggers for systemd (237-3ubuntu10.53)
Puis on récupère depuis le git de Dalibo les sources à la dernière version disponible :
root@sarah:~# git clone <a href="https://gitlab.com/dalibo/postgresql_anonymizer.git">https://gitlab.com/dalibo/postgresql_anonymizer.git</a> Cloning into 'postgresql_anonymizer'... remote: Enumerating objects: 5145, done. remote: Counting objects: 100% (487/487), done. remote: Compressing objects: 100% (271/271), done. remote: Total 5145 (delta 327), reused 277 (delta 216), pack-reused 4658 Receiving objects: 100% (5145/5145), 25.71 MiB | 19.23 MiB/s, done. Resolving deltas: 100% (3304/3304), done.
Une fois les sources récupérées, on se positionne dans le répertoire créé par Git ou les sources ont été déposées et on fait un make extension :
root@sarah:~/postgresql_anonymizer# make extension mkdir -p anon cp anon.sql anon/anon--1.1.0.sql cp data/*.csv anon/ cp python/populate.py anon/
Et enfin un Make install pour installer le tout. Il est important de préciser qu’il est nécessaire que vous ayez installé gcc pour pouvoir compiler les sources de l’extension (apt install gcc) :
root@sarah:~/postgresql_anonymizer# sudo make install cp anon.sql anon/anon--1.1.0.sql cp data/*.csv anon/ cp python/populate.py anon/ gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing- format-attribute -Wimplicit-fallthrough=3 -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno- format-truncation -g -g -O2 -fstack-protector-strong -Wformat -Werror= format-security -fno-omit-frame-pointer -fPIC -Wno-unused-variable -I. -I./ -I/usr/include/postgresql/13/server -I/usr/include/postgresql/internal -Wdate- time -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -I/usr/include/libxml2 -c -o anon.o anon.c gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing- format-attribute -Wimplicit-fallthrough=3 -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno- format-truncation -g -g -O2 -fstack-protector-strong -Wformat -Werror= format-security -fno-omit-frame-pointer -fPIC -Wno-unused-variable anon.o -L/usr/lib/x86_64-linux-gnu -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -L/usr/lib/llvm-6.0/lib -Wl,--as-needed -shared -o anon.so /usr/bin/clang-6.0 -Wno-ignored-attributes -fno-strict-aliasing -fwrapv -Wno-unused- command-line-argument -O2 -I. -I./ -I/usr/include/postgresql/13/server -I/usr/include/postgresql/internal -Wdate- time -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -I/usr/include/libxml2 -flto=thin -emit-llvm -c -o anon. bc anon.c /bin/mkdir -p '/usr/share/postgresql/15/extension' /bin/mkdir -p '/usr/share/postgresql/15/extension/anon' /bin/mkdir -p '/usr/lib/postgresql/15/lib' install -d /usr/lib/postgresql/15/bin install -m 0755 bin/pg_dump_anon.sh /usr/lib/postgresql/15/bin /usr/bin/install -c -m 644 .//anon.control '/usr/share/postgresql/15/extension/' /usr/bin/install -c -m 644 .//anon/* '/usr/share/postgresql/15/extension/anon/' /usr/bin/install -c -m 755 anon.so '/usr/lib/postgresql/15/lib/' /bin/mkdir -p '/usr/lib/postgresql/15/lib/bitcode/anon' /bin/mkdir -p '/usr/lib/postgresql/15/lib/bitcode'/anon/ /usr/bin/install -c -m 644 anon. bc '/usr/lib/postgresql/15/lib/bitcode'/anon/./ cd '/usr/lib/postgresql/15/lib/bitcode' && /usr/lib/llvm-6.0/bin/llvm-lto -thinlto -thinlto-action=thinlink -o anon.index. bc anon/anon. bc
Pour ce test, j’ai importé la base de données exemple dvdrental de PostgreSQL. Elle me permettra d’illustrer la pseudonymisation facilement et sur une quantité respectable de données. Pour importer cette base de données, rien de plus simple :
On la récupère en la téléchargeant sur le site d’hébergement :
postgres@sarah:~$ wget https://www.postgresqltutorial.com/wp-content/uploads/2019/05/dvdrental.zip --2023-11-29 13:42:52-- https://www.postgresqltutorial.com/wp-content/uploads/2019/05/dvdrental.zip Resolving www.postgresqltutorial.com (www.postgresqltutorial.com)... 104.21.2.174, 172.67.129.129, 2606:4700:3037::6815:2ae, ... Connecting to www.postgresqltutorial.com (www.postgresqltutorial.com)|104.21.2.174|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 550906 (538K) [application/zip] Saving to: ‘dvdrental.zip’ dvdrental.zip 100%[========================================================================================================================================>] 537.99K --.-KB/s in 0.01s 2023-11-29 13:42:52 (45.5 MB/s) - ‘dvdrental.zip’ saved [550906/550906]
On la dézippe ensuite :
postgres@sarah:~$ unzip dvdrental.zip Archive: dvdrental.zip inflating: dvdrental.tar
On obtient ainsi une archive qu’on peut utiliser avec un pg_restore pour charger la base de données. Je me suis d’abbord connectée sur l’instance pour créer une base de données que j’ai appelé dvdrental:
postgres@ip-172-44-2-72:~$ psql psql (15.5 (Ubuntu 15.5-1.pgdg22.04+1)) Type "help" for help.
postgres=# create database dvdrental; CREATE DATABASE
Puis j’ai restauré la base :
postgres@ip-172-44-2-72:~$ pg_restore -U postgres -d dvdrental dvdrental.tar
Nous sommes donc prêts à commencer.
Il existe un petit nombre de fonctions de pseudonymisation dans PostgreSQL anonymiser. Elles fonctionnent comme les fonctions d’anonymisation et peuvent être utilisée en masquage statique ou en masquage dynamique.
Les fonctions disponibles sont les suivantes :
anon.pseudo_first_name('seed','salt')
qui retourne un prénom génériqueanon.pseudo_last_name('seed','salt')
qui retourne un nom de famille génériqueanon.pseudo_email('seed','salt')
qui retourne une adresse email générique et existanteanon.pseudo_city('seed','salt')
qui retourne le nom d’une ville existanteanon.pseudo_country('seed','salt')
qui retourne un pays existantanon.pseudo_company('seed','salt')
qui retourne un nom de société génériqueanon.pseudo_iban('seed','salt')
qui retourne un IBAN valideanon.pseudo_siret('seed','salt') qui retourne un SIRET valide
Il est important de définir un salt différent pour chaque base de données. Si un utilisateur quelconque trouve le salt de votre base de données, il sera ensuite capable de procéder à une attaque par force brute sur toutes les autres données afin de les révéler.
dvdrental=# alter database dvdrental set anon.salt = 'This_is_a_salt';
Pour prendre un exemple, nous pouvons tenter de pseudonymiser les données des clients de notre base dvdrental :
dvdrental=# select * from customer limit 5; customer_id | store_id | first_name | last_name | email | address_id | activebool | create_date | last_update | active -------------+----------+------------+-----------+-------------------------------------+------------+------------+-------------+-------------------------+-------- 524 | 1 | Jared | Ely | jared.ely@sakilacustomer.org | 530 | t | 2006-02-14 | 2013-05-26 14:49:45.738 | 1 1 | 1 | Mary | Smith | mary.smith@sakilacustomer.org | 5 | t | 2006-02-14 | 2013-05-26 14:49:45.738 | 1 2 | 1 | Patricia | Johnson | patricia.johnson@sakilacustomer.org | 6 | t | 2006-02-14 | 2013-05-26 14:49:45.738 | 1 3 | 1 | Linda | Williams | linda.williams@sakilacustomer.org | 7 | t | 2006-02-14 | 2013-05-26 14:49:45.738 | 1 4 | 2 | Barbara | Jones | barbara.jones@sakilacustomer.org | 8 | t | 2006-02-14 | 2013-05-26 14:49:45.738 | 1 (5 rows)
Lors d’un premier essai, en suivant la documentation officielle disponible ici, j’ai utilisé la fonction suivante :
dvdrental=# SECURITY LABEL FOR anon ON COLUMN customer.first_name is 'MASKED WITH FUNCTION anon.pseudo_first_name('seed', 'salt')'; SECURITY LABEL
Au moment d’utiliser le masquage pour pseudonymiser mes données, je suis tombée sur l’erreur suivante :
dvdrental=# SECURITY LABEL FOR anon ON COLUMN customer.first_name IS 'MASKED WITH FUNCTION anon.pseudo_first_name('seed','salt')'; ERROR: syntax error at or near "seed" LINE 2: IS 'MASKED WITH FUNCTION anon.pseudo_first_name('seed','salt...
Il faut le savoir, car ce n’est pas forcément bien explicité dans la documentation : le simple guillemet ne permet pas d’échapper correctement les caractères. Il est donc nécessaire d’ajouter d’autres caractères d’échappement. Dans ce cas, j’ai choisi d’utiliser le symbole “$”.
Ainsi, au deuxième essai, cela m’a donné :
dvdrental=# SECURITY LABEL FOR anon ON COLUMN customer.first_name is 'MASKED WITH FUNCTION anon.pseudo_first_name($$seed$$, $$salt$$)'; SECURITY LABEL
Ca avait l’air de fonctionner, j’ai donc lancé mon anonymisation pour vérifier :
dvdrental=# SELECT anon.anonymize_database(); DEBUG: Anonymize table public.customer with first_name = anon.pseudo_first_name($$seed$$) ERROR: could not determine polymorphic type because input has type unknown CONTEXT: SQL statement "UPDATE public.customer SET first_name = anon.pseudo_first_name($$seed$$)" PL/pgSQL function anon.anonymize_table(regclass) line 38 at EXECUTE SQL function "anonymize_database" statement 1
Il s’avère que la documentation ne précise pas qu’il faut typer les deux champs qu’on utilise pour notre fonction, sinon PostgreSQL ne sait pas quoi en faire. Cela donne donc :
dvdrental=# SECURITY LABEL FOR anon ON COLUMN customer.first_name is 'MASKED WITH FUNCTION anon.pseudo_first_name(CAST($$seed$$ as text), cast($$salt$$ as text))'; SECURITY LABEL
Et on applique ensuite les différentes fonctions :
dvdrental=# SELECT anon.anonymize_database(); DEBUG: Anonymize table public.customer with first_name = anon.pseudo_first_name(CAST($$seed$$ as text), cast($$salt$$ as text)) anonymize_database -------------------- t (1 row)
On va ensuite vérifier nos données dans la table pour voir si cela a été appliqué :
dvdrental=# select * from customer limit 5; customer_id | store_id | first_name | last_name | email | address_id | activebool | create_date | last_update | active -------------+----------+------------+-----------+-----------------------------------+------------+------------+-------------+----------------------------+-------- 524 | 1 | Taniya | Ely | jared.ely@sakilacustomer.org | 530 | t | 2006-02-14 | 2024-01-29 09:26:48.268084 | 1 15 | 1 | Taniya | Harris | helen.harris@sakilacustomer.org | 19 | t | 2006-02-14 | 2024-01-29 09:26:48.268084 | 1 16 | 2 | Taniya | Martin | sandra.martin@sakilacustomer.org | 20 | t | 2006-02-14 | 2024-01-29 09:26:48.268084 | 0 17 | 1 | Taniya | Thompson | donna.thompson@sakilacustomer.org | 21 | t | 2006-02-14 | 2024-01-29 09:26:48.268084 | 1 18 | 2 | Taniya | Garcia | carol.garcia@sakilacustomer.org | 22 | t | 2006-02-14 | 2024-01-29 09:26:48.268084 | 1 (5 rows)
On se rend alors compte que toutes les données pseudonymisées l’ont été avec le même résultat.
dvdrental=# select distinct first_name from customer; first_name ------------ Taniya (1 row)
Il est en effet précisé dans la documentation que si on voulait obtenir des pseudo différents pour chaque ligne, il fallait ajouter un custom dataset d’un nombre supérieur de ligne au nombres d’entrées dans notre table.
Il existe un dataset fournit par dalibo (en français uniquement), disponible à l’adresse suivante .
Pour pouvoir l’intégrer dans votre extension, il vous faut le télécharger sur votre serveur, le placer dans le dossier de votre choix, et utiliser la commande :
dvdrental=#SELECT anon.init('/path/to/custom_csv_files/')
Vous pouvez également créer votre propre dataset sous la forme d’un fichier csv avec un script par exemple, pour peupler vos exemples avec vos propres données personnalisées.
On peut tenter de réaliser la même opération mais pour une autre donnée. On voit en effet que notre adresse email contient toujours les noms de familles des personnes, ils n’ont pas été pseudonymisés. On va donc changer cela :
dvdrental=# security label for anon on column customer.email is 'MASKED WITH FUNCTION anon.pseudo_email(CAST($$seed$$ as text), cast($$salt$$ as text))'; SECURITY LABEL
Et une fois appliqué on obtient les données suivantes :
dvdrental=# select * from customer limit 5; customer_id | store_id | first_name | last_name | email | address_id | activebool | create_date | last_update | active -------------+----------+------------+-----------+----------------------------+------------+------------+-------------+----------------------------+-------- 524 | 1 | Taniya | Ely | rowesally@kelly-dorsey.com | 530 | t | 2006-02-14 | 2024-01-29 09:42:03.560633 | 1 235 | 1 | Taniya | Lynch | rowesally@kelly-dorsey.com | 239 | t | 2006-02-14 | 2024-01-29 09:42:03.560633 | 1 15 | 1 | Taniya | Harris | rowesally@kelly-dorsey.com | 19 | t | 2006-02-14 | 2024-01-29 09:42:03.560633 | 1 16 | 2 | Taniya | Martin | rowesally@kelly-dorsey.com | 20 | t | 2006-02-14 | 2024-01-29 09:42:03.560633 | 0 17 | 1 | Taniya | Thompson | rowesally@kelly-dorsey.com | 21 | t | 2006-02-14 | 2024-01-29 09:42:03.560633 | 1 (5 rows)
Les limites de la pseudonymisation Postgres :
Actuellement il n’existe qu’une seule extension permettant de faire de la pseudonymisation sur PostgreSQL. Et elle présente plusieurs limites :
- Les fonctions de pseudonymisation de postgresql anonymizer sont déterministes. C’est à dire que pour deux valeurs identiques, elles donneront toujours le même résultat.
- Les données pseudonymisée restent des données personnes ! Le RGPD précise très clairement que : « les données à caractère personnel qui ont fait l’objet d’une pseudonymisation […] devraient être considérées comme des informations concernant une personne physique identifiable. »
- Les coûts en performance, comme évoqué dans mon précédent article pour cette même extension, sont élevés. C’est quelque chose à considérer quand on veut utiliser cette méthode.
- Il faut pouvoir gérer correctement les clés qu’on utilises pour la pseudonymisation, c’est une charge supplémentaire.
- La documentation n’est pas forcément tout à fait bien mise à jour, et certaines choses ne sont pas évidentes à appréhender.
- L’extension est toujours en développement et en changements constants, nous ne sommes pas à l’abris de rencontrer des bugs au fil des versions. Pour écrire cet article, j’ai du contacter directement le développeur car au début de sa rédaction, les fonctions de pseudonymisation ne fonctionnaient pas.
Conclusion :
En conclusion, la pseudonymisation des données dans PostgreSQL se révèle être une stratégie de protection de la vie privée et de conformité réglementaire particulièrement robuste. En adoptant cette approche, les entreprises peuvent tirer parti des avantages significatifs tels que la préservation de la confidentialité des données tout en permettant l’analyse et le traitement des informations sensibles.
D’un côté, la pseudonymisation offre une solution efficace pour équilibrer la nécessité d’accéder aux données avec le respect des réglementations de confidentialité. Les données pseudonymisées demeurent utiles pour les analyses internes tout en limitant le risque d’exploitation malveillante.
Cependant, il est crucial de reconnaître que la pseudonymisation n’est pas une panacée. Elle peut présenter des défis en termes de gestion des clés de pseudonymisation, de complexité accrue dans la maintenance des bases de données, et de potentielles vulnérabilités si elle est mal mise en œuvre.
Dans une perspective plus large, il est également pertinent de considérer l’anonymisation des données comme une alternative. Bien que l’anonymisation puisse offrir un niveau supérieur de protection, elle peut également rendre les données moins utiles pour certaines applications, limitant ainsi leur valeur pour les analyses internes.
En définitive, le choix entre la pseudonymisation et l’anonymisation dépend des besoins spécifiques de chaque organisation, du contexte réglementaire et des compromis acceptables entre la protection de la vie privée et l’utilité des données. En élaborant une stratégie de gestion des données judicieuse, les entreprises peuvent naviguer avec succès dans le paysage complexe de la confidentialité des données dans PostgreSQL.
Continuez votre lecture sur le blog :
- PostgreSQL Anonymizer (Sarah FAVEERE) [PostgreSQL]
- pg_recursively_delete : Simplifier les suppressions récursives (Sarah FAVEERE) [PostgreSQL]
- pg_dirtyread où comment réparer facilement un delete sauvage (Sarah FAVEERE) [PostgreSQL]
- PostgreSQL : planifier une tâche avec pg_cron (Emmanuel RAMI) [Non classéPostgreSQL]
- HypoPG et les index hypothétiques (Sarah FAVEERE) [PostgreSQL]