Le RGPD impose des obligations techniques précises aux hébergeurs et responsables de traitement. Au-delà des aspects juridiques, cet article détaille les implémentations concrètes : chiffrement, pseudonymisation, journalisation et droits des utilisateurs.
Plan
- Obligations techniques du RGPD
- Chiffrement des données au repos et en transit
- Pseudonymisation et anonymisation
- Journalisation et traçabilité
- Gestion des droits utilisateurs
- Procédures de violation de données
- Conclusion
Obligations techniques du RGPD
Le RGPD (Règlement Général sur la Protection des Données) impose :
Article 32 - Sécurité du traitement :
- Chiffrement des données personnelles
- Capacité à garantir la confidentialité, l'intégrité et la disponibilité
- Procédure de test et d'évaluation régulière
Article 25 - Privacy by design :
- Pseudonymisation dès la conception
- Minimisation des données collectées
- Mesures techniques appropriées
Article 33 - Notification de violation :
- Détection sous 72h
- Documentation de l'incident
- Notification à la CNIL et aux personnes concernées
Chiffrement des données au repos et en transit
Chiffrement en transit (TLS)
# Nginx avec TLS 1.3 uniquement
# /etc/nginx/nginx.conf
ssl_protocols TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;
# HSTS (force HTTPS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Test de configuration :
# Vérifier la configuration TLS
nmap --script ssl-enum-ciphers -p 443 example.com
# Ou avec testssl.sh
./testssl.sh https://example.com
Chiffrement au repos
Base de données PostgreSQL :
# postgresql.conf
ssl = on
ssl_cert_file = '/etc/ssl/certs/server.crt'
ssl_key_file = '/etc/ssl/private/server.key'
ssl_ca_file = '/etc/ssl/certs/root.crt'
# Chiffrement transparent des données (TDE)
# Utiliser pgcrypto pour colonnes sensibles
CREATE EXTENSION pgcrypto;
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255),
password_hash BYTEA,
ssn BYTEA -- Numéro de sécurité sociale chiffré
);
-- Insertion avec chiffrement
INSERT INTO users (email, ssn)
VALUES ('user@example.com', pgp_sym_encrypt('123-45-6789', 'encryption_key'));
-- Lecture avec déchiffrement
SELECT email, pgp_sym_decrypt(ssn, 'encryption_key') AS ssn FROM users;
Chiffrement disque (LUKS) :
# Chiffrer une partition
cryptsetup luksFormat /dev/sdb1
# Ouvrir la partition chiffrée
cryptsetup luksOpen /dev/sdb1 encrypted_data
# Formater et monter
mkfs.ext4 /dev/mapper/encrypted_data
mount /dev/mapper/encrypted_data /mnt/secure
# Auto-mount au démarrage
echo "encrypted_data UUID=$(blkid -s UUID -o value /dev/sdb1) none luks" >> /etc/crypttab
echo "/dev/mapper/encrypted_data /mnt/secure ext4 defaults 0 2" >> /etc/fstab
Chiffrement fichiers avec GPG :
# Chiffrer un backup
tar czf - /var/www | gpg --symmetric --cipher-algo AES256 -o backup.tar.gz.gpg
# Déchiffrer
gpg -d backup.tar.gz.gpg | tar xzf -
Pseudonymisation et anonymisation
Pseudonymisation : remplacer les identifiants directs
# Script de pseudonymisation Python
import hashlib
import hmac
SECRET_KEY = "votre_cle_secrete_rgpd"
def pseudonymize(identifier):
"""Pseudonymise un identifiant (email, ID) de façon déterministe"""
return hmac.new(
SECRET_KEY.encode(),
identifier.encode(),
hashlib.sha256
).hexdigest()[:16]
# Exemple
email = "user@example.com"
pseudo_id = pseudonymize(email)
print(f"{email} → {pseudo_id}") # user@example.com → a3f5e9c2b8d1f7a4
En base de données :
-- Table de mapping (à protéger strictement)
CREATE TABLE user_mapping (
real_id INT PRIMARY KEY,
pseudo_id VARCHAR(16) UNIQUE NOT NULL
);
-- Table analytics avec données pseudonymisées
CREATE TABLE analytics (
id SERIAL PRIMARY KEY,
pseudo_user_id VARCHAR(16),
action VARCHAR(50),
timestamp TIMESTAMP,
FOREIGN KEY (pseudo_user_id) REFERENCES user_mapping(pseudo_id)
);
Anonymisation : suppression irréversible des identifiants
-- Anonymiser des données après expiration de rétention
UPDATE users
SET
email = CONCAT('deleted_', id, '@anonymized.local'),
name = 'Deleted User',
phone = NULL,
address = NULL,
birthdate = NULL
WHERE last_login < NOW() - INTERVAL '3 years'
AND deletion_requested = TRUE;
Journalisation et traçabilité
Logs d'accès aux données personnelles
# Auditd pour tracer les accès fichiers
# /etc/audit/rules.d/rgpd.rules
-w /var/lib/postgresql/data -p rwa -k database_access
-w /var/www/uploads -p rwa -k user_files_access
-a always,exit -F arch=b64 -S open,openat -F dir=/home/customers -k customer_data_access
Application web - middleware de logging :
# Exemple Django middleware
import logging
from django.utils.deprecation import MiddlewareMixin
logger = logging.getLogger('rgpd.access')
class RGPDAccessLogMiddleware(MiddlewareMixin):
def process_view(self, request, view_func, view_args, view_kwargs):
if request.user.is_authenticated:
# Log l'accès aux données personnelles
if 'user_id' in view_kwargs or '/profile/' in request.path:
logger.info(
f"Access to personal data | "
f"User: {request.user.id} | "
f"IP: {request.META.get('REMOTE_ADDR')} | "
f"Path: {request.path} | "
f"Method: {request.method}"
)
return None
Conservation et rotation des logs
# /etc/logrotate.d/rgpd-logs
/var/log/rgpd/*.log {
daily
rotate 90 # Conserver 90 jours minimum (recommandé CNIL)
compress
delaycompress
notifempty
create 0640 root adm
sharedscripts
postrotate
systemctl reload rsyslog > /dev/null 2>&1 || true
endscript
}
Gestion des droits utilisateurs (DSAR)
Droit d'accès : export des données
# Script d'export des données utilisateur
import json
from datetime import datetime
def export_user_data(user_id):
"""Exporte toutes les données d'un utilisateur (RGPD Article 15)"""
user = User.objects.get(id=user_id)
data = {
"export_date": datetime.now().isoformat(),
"user_info": {
"email": user.email,
"name": user.name,
"created_at": user.created_at.isoformat(),
},
"orders": [
{
"id": order.id,
"date": order.date.isoformat(),
"total": str(order.total),
"items": order.items
}
for order in user.orders.all()
],
"activity_logs": [
{
"action": log.action,
"timestamp": log.timestamp.isoformat()
}
for log in user.activity_logs.all()
]
}
filename = f"user_{user_id}_data_{datetime.now().strftime('%Y%m%d')}.json"
with open(f"/tmp/{filename}", 'w') as f:
json.dump(data, f, indent=2)
return filename
Droit à l'effacement (droit à l'oubli)
def delete_user_data(user_id, reason):
"""Supprime les données utilisateur (RGPD Article 17)"""
# 1. Logger la demande
log_deletion_request(user_id, reason)
# 2. Anonymiser au lieu de supprimer (pour garder l'intégrité référentielle)
user = User.objects.get(id=user_id)
user.email = f"deleted_{user_id}@anonymized.local"
user.name = "Deleted User"
user.phone = None
user.address = None
user.is_active = False
user.deletion_date = datetime.now()
user.save()
# 3. Supprimer les données non critiques
user.profile_picture.delete()
user.preferences.delete()
user.saved_items.all().delete()
# 4. Notifier les sous-traitants
notify_data_processors(user_id, 'deletion')
return True
Portabilité des données
def export_portable_format(user_id):
"""Export au format portable (JSON, CSV) pour transfert à un autre service"""
user = User.objects.get(id=user_id)
# Format structuré et interopérable
portable_data = {
"schema_version": "1.0",
"data_controller": "VotreEntreprise SAS",
"export_date": datetime.now().isoformat(),
"user": {
"email": user.email,
"profile": {
"name": user.name,
"birthdate": user.birthdate.isoformat() if user.birthdate else None,
},
"subscriptions": [s.to_dict() for s in user.subscriptions.all()],
"content": [c.to_dict() for c in user.content.all()]
}
}
return portable_data
Procédures de violation de données
Détection et notification sous 72h
#!/bin/bash
# Script de notification de violation RGPD
# /usr/local/bin/rgpd-breach-notification.sh
INCIDENT_ID=$1
DESCRIPTION=$2
AFFECTED_USERS=$3
# 1. Logger l'incident
echo "[$(date)] BREACH DETECTED: $INCIDENT_ID - $DESCRIPTION" >> /var/log/rgpd/breaches.log
# 2. Alerter l'équipe sécurité immédiatement
mail -s "🚨 RGPD Data Breach: $INCIDENT_ID" security@example.com <<EOF
Violation de données détectée:
ID: $INCIDENT_ID
Description: $DESCRIPTION
Utilisateurs affectés: $AFFECTED_USERS
Date: $(date)
Actions requises:
1. Investigation immédiate
2. Notification CNIL sous 72h
3. Notification utilisateurs affectés
4. Mise en place des mesures correctives
EOF
# 3. Déclencher l'investigation
/usr/local/bin/start-incident-response.sh $INCIDENT_ID
Documentation de l'incident
# Registre des violations (obligatoire RGPD)
class DataBreach(models.Model):
incident_id = models.CharField(max_length=50, unique=True)
detected_at = models.DateTimeField(auto_now_add=True)
description = models.TextField()
affected_users_count = models.IntegerField()
data_categories = models.JSONField() # Types de données concernées
# Actions prises
containment_measures = models.TextField()
notification_cnil_at = models.DateTimeField(null=True)
notification_users_at = models.DateTimeField(null=True)
# Statut
status = models.CharField(max_length=20) # detected, contained, resolved
resolved_at = models.DateTimeField(null=True)
# DPO
dpo_notified = models.BooleanField(default=False)
class Meta:
db_table = 'rgpd_breaches'
Conclusion
La conformité RGPD technique repose sur :
- Chiffrement : TLS 1.3 en transit, LUKS/pgcrypto au repos
- Pseudonymisation : HMAC-SHA256 pour les identifiants
- Journalisation : auditd + logs applicatifs avec rétention 90j+
- Droits utilisateurs : export automatisé, suppression/anonymisation
- Violation : détection, notification sous 72h, registre documenté
Actions prioritaires :
- Auditer les données personnelles traitées
- Implémenter le chiffrement (transit + repos)
- Mettre en place la journalisation des accès
- Créer les procédures DSAR (export, suppression)
- Tester le plan de réponse aux incidents
Ressources complémentaires
- Hébergez vos données en cloud souverain pour renforcer la conformité
- Utilisez Vault pour la gestion des secrets
- Sécurisez vos communications avec mTLS et TLS avancée
- Centralisez vos logs pour le monitoring


