Entreprise
Sécurité

Conformité RGPD technique : chiffrement, pseudonymisation, logs d'accès

15 janvier 2026

7 min de lecture

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 :

  1. Auditer les données personnelles traitées
  2. Implémenter le chiffrement (transit + repos)
  3. Mettre en place la journalisation des accès
  4. Créer les procédures DSAR (export, suppression)
  5. Tester le plan de réponse aux incidents

Ressources complémentaires

Besoin d'aide sur ce sujet ?

Notre équipe d'experts est là pour vous accompagner dans vos projets d'infrastructure et d'infogérance.

Contactez-nous

Articles similaires