⚖️
Bonnes pratiques
RGPD et conformité sécurité : obligations et mise en œuvre
📅 2025-06-14 ⏱ 10 min de lecture 🏷 Débutant

// SOMMAIRE

RGPD et conformité sécurité : obligations et mise en œuvre

Le RGPD (Règlement Général sur la Protection des Données) est entré en vigueur en mai 2018. Au-delà des amendes, il impose une vraie culture de la sécurité des données personnelles. Ce guide traduit les obligations légales en actions techniques concrètes.

Les principes fondamentaux du RGPD

6 principes clés (Article 5) :

  • LICÉITÉ, LOYAUTÉ, TRANSPARENCE
  • → Base légale pour chaque traitement

    → Informer les personnes clairement

  • LIMITATION DES FINALITÉS
  • → Collecter uniquement pour un but précis et déclaré

    → Pas de réutilisation pour d'autres fins

  • MINIMISATION DES DONNÉES
  • → Ne collecter QUE ce qui est nécessaire

    → Pas de collecte "au cas où"

  • EXACTITUDE
  • → Données à jour, possibilité de correction

  • LIMITATION DE LA CONSERVATION
  • → Durées de conservation définies et respectées

    → Suppression après la durée

  • INTÉGRITÉ ET CONFIDENTIALITÉ
  • → Sécurité technique et organisationnelle appropriée

    Privacy by Design et Privacy by Default

    # Privacy by Design : intégrer la vie privée dès la conception
    
    

    ❌ Mauvaise approche

    class UserProfile:

    def __init__(self, user_data):

    # Stocker TOUT sans réfléchir

    self.name = user_data['name']

    self.email = user_data['email']

    self.phone = user_data['phone']

    self.address = user_data['address']

    self.birth_date = user_data['birth_date']

    self.ip_history = user_data.get('ip_history', [])

    self.browsing_history = user_data.get('browsing', [])

    self.location_history = user_data.get('locations', [])

    # On stocke tout "au cas où"

    ✅ Bonne approche - Privacy by Design

    class UserProfile:

    def __init__(self, user_data, purpose="newsletter"):

    # Ne collecter que ce qui est NÉCESSAIRE pour la finalité

    if purpose == "newsletter":

    # Pour une newsletter : seulement email + préférence langue

    self.email = user_data['email']

    self.language = user_data.get('language', 'fr')

    # Pas besoin de nom, adresse, téléphone !

    elif purpose == "delivery":

    # Pour une livraison : nom + adresse uniquement

    self.name = user_data['name']

    self.delivery_address = user_data['address']

    # Pas besoin de date de naissance, historique nav !

    # Pas de collecte des IP, tracking, comportement sans base légale explicite

    Les bases légales du RGPD

    bases_legales = {
    

    "consentement": {

    "art": "Art. 6(1)(a)",

    "description": "Consentement libre, spécifique, éclairé et univoque",

    "exemple": "Newsletter : case à cocher non pré-cochée",

    "retrait": "Aussi facile que de donner le consentement"

    },

    "contrat": {

    "art": "Art. 6(1)(b)",

    "description": "Nécessaire à l'exécution d'un contrat",

    "exemple": "Adresse pour livraison d'une commande",

    "attention": "Pas de traitement supplémentaire 'lié'"

    },

    "obligation_legale": {

    "art": "Art. 6(1)(c)",

    "description": "Obligation légale du responsable",

    "exemple": "Facturation (conservation 10 ans), déclaration fiscale"

    },

    "interet_legitime": {

    "art": "Art. 6(1)(f)",

    "description": "Intérêt légitime sous conditions",

    "exemple": "Logs de sécurité, prévention de fraude",

    "condition": "Doit être mis en balance avec les droits des personnes"

    }

    }

    Mise en œuvre technique

    Chiffrement et pseudonymisation

    import hashlib
    

    import secrets

    from cryptography.fernet import Fernet

    class DataProtection:

    def pseudonymise(self, identifier, salt=None):

    """

    Pseudonymisation : remplace l'identifiant par un pseudonyme

    Le lien peut être rétabli avec la table de correspondance

    """

    if not salt:

    salt = secrets.token_hex(16)

    pseudonym = hashlib.sha256(f"{identifier}{salt}".encode()).hexdigest()

    # Stocker le lien identifier → pseudonym dans une table séparée et sécurisée

    return pseudonym, salt

    def anonymise(self, data):

    """

    Anonymisation irréversible : plus aucun lien possible

    Les données anonymisées ne sont plus soumises au RGPD

    """

    # K-anonymity : regrouper les données pour qu'elles

    # s'appliquent à au moins k individus

    # Exemple : âge exact → tranche d'âge

    result = {}

    if 'age' in data:

    result['age_range'] = f"{(data['age'] // 10) 10}-{(data['age'] // 10) 10 + 9}"

    if 'zip_code' in data:

    result['region'] = data['zip_code'][:2] # Département seulement

    return result

    def encrypt_personal_data(self, data, key=None):

    """Chiffrement des données personnelles au repos"""

    if not key:

    key = Fernet.generate_key()

    f = Fernet(key)

    encrypted = f.encrypt(data.encode())

    return encrypted, key

    Gestion du consentement

    // Gestionnaire de consentement RGPD-compliant
    
    

    class ConsentManager {

    constructor() {

    this.consents = {};

    this.consentLog = []; // Audit trail obligatoire

    }

    requestConsent(purpose, description) {

    // Le consentement doit être :

    // ✅ Libre (pas de service conditionné au consentement marketing)

    // ✅ Spécifique (un consentement par finalité)

    // ✅ Éclairé (description claire)

    // ✅ Univoque (action positive, pas de case pré-cochée)

    return {

    purpose,

    description,

    timestamp: new Date().toISOString(),

    checkboxPreChecked: false, // JAMAIS pré-coché

    bundled: false // JAMAIS groupé avec d'autres consentements

    };

    }

    recordConsent(userId, purpose, given, proofOfConsent) {

    // Enregistrement pour preuve (Article 7 RGPD)

    this.consentLog.push({

    userId,

    purpose,

    given,

    timestamp: new Date().toISOString(),

    ipAddress: proofOfConsent.ip, // Pour preuve

    userAgent: proofOfConsent.userAgent,

    consentText: proofOfConsent.text // Texte exact présenté

    });

    this.consents[${userId}_${purpose}] = given;

    }

    withdrawConsent(userId, purpose) {

    // Le retrait doit être aussi simple que le don

    this.recordConsent(userId, purpose, false, { action: 'withdrawal' });

    // Déclencher la suppression des données traitées sur cette base

    this.deleteDataForPurpose(userId, purpose);

    }

    }

    Droits des personnes

    class GDPRRightsHandler:
    
    

    def handle_access_request(self, user_id):

    """

    Droit d'accès (Art. 15) :

    Répondre dans 1 mois, données lisibles, format portable

    """

    user_data = {

    'personal_data': self.get_all_user_data(user_id),

    'processing_purposes': self.get_purposes(user_id),

    'data_recipients': self.get_recipients(user_id),

    'retention_period': self.get_retention_info(),

    'rights_info': "Vous pouvez demander rectification, suppression ou portabilité"

    }

    return user_data

    def handle_erasure_request(self, user_id):

    """

    Droit à l'effacement (Art. 17) - "Droit à l'oubli"

    Conditions : plus nécessaire, retrait consentement, opposition...

    Exceptions : obligation légale, intérêt public

    """

    # Vérifier si des exceptions s'appliquent

    if self.has_legal_retention_obligation(user_id):

    return {"status": "partial", "reason": "Obligation légale de conservation"}

    # Supprimer de toutes les bases de données

    self.delete_from_main_db(user_id)

    self.delete_from_analytics(user_id)

    self.delete_from_backups_on_schedule(user_id) # Backups : prochaine rotation

    self.notify_third_parties(user_id) # Informer les destinataires

    # Documenter la suppression

    self.log_erasure(user_id, datetime.now())

    return {"status": "success", "completion_date": "Dans les 30 jours pour les backups"}

    def handle_portability_request(self, user_id):

    """

    Droit à la portabilité (Art. 20) :

    Format structuré, couramment utilisé, machine-readable (JSON/CSV)

    """

    data = self.get_all_user_data(user_id)

    return {

    'format': 'JSON',

    'data': data,

    'export_date': datetime.now().isoformat()

    }

    Registre des traitements (Article 30)

    # Tout responsable de traitement doit tenir un registre
    
    

    registre_traitements = [

    {

    "nom": "Gestion des comptes utilisateurs",

    "finalite": "Permettre l'accès au service",

    "base_legale": "Contrat",

    "categories_donnees": ["Nom", "Email", "Mot de passe hashé"],

    "categories_personnes": "Utilisateurs du service",

    "destinataires": ["Équipe IT interne"],

    "pays_tiers": None,

    "duree_conservation": "Durée du compte + 3 ans après désactivation",

    "mesures_securite": "Chiffrement AES-256, HTTPS, 2FA disponible"

    },

    {

    "nom": "Analytics et statistiques",

    "finalite": "Améliorer le service",

    "base_legale": "Intérêt légitime",

    "categories_donnees": ["IP anonymisée", "Pages visitées", "Durée session"],

    "categories_personnes": "Visiteurs du site",

    "destinataires": ["Matomo (self-hosted)"],

    "pays_tiers": None,

    "duree_conservation": "13 mois",

    "mesures_securite": "IP anonymisée, pas de cookies tiers, hébergement EU"

    }

    ]

    Notification de violation de données (Article 33/34)

    class DataBreachNotification:
    
    

    def assess_breach(self, breach_details):

    """

    Évaluer si la violation doit être notifiée :

    - À la CNIL : dans 72h si risque pour les personnes

    - Aux personnes : si risque ÉLEVÉ pour leurs droits et libertés

    """

    risk_score = 0

    # Facteurs de risque

    if breach_details.get('data_encrypted'):

    risk_score -= 2 # Données chiffrées = risque réduit

    if breach_details.get('data_type') == 'health':

    risk_score += 3

    if breach_details.get('data_type') == 'financial':

    risk_score += 3

    if breach_details.get('affected_count') > 1000:

    risk_score += 2

    if breach_details.get('sensitive_categories'):

    risk_score += 3

    return {

    'notify_cnil': risk_score > 0, # 72h max

    'notify_individuals': risk_score > 3,

    'risk_level': 'high' if risk_score > 3 else 'medium' if risk_score > 0 else 'low'

    }

    def generate_cnil_notification(self, breach):

    """Contenu requis par la CNIL"""

    return {

    'nature_violation': breach['type'],

    'categories_personnes': breach['affected_categories'],

    'nombre_approximatif': breach['affected_count'],

    'categories_donnees': breach['data_types'],

    'consequences_probables': breach['likely_consequences'],

    'mesures_prises': breach['measures_taken'],

    'contact_dpo': self.get_dpo_contact(),

    'date_constatation': breach['discovery_date']

    }

    Amendes RGPD

    Niveaux d'amendes :
    
    

    Niveau 1 (infractions mineures) :

    → Max : 10 millions € ou 2% du CA mondial

    → Exemples : registre des traitements absent, DPO non désigné

    Niveau 2 (infractions graves) :

    → Max : 20 millions € ou 4% du CA mondial

    → Exemples : violation des principes, base légale absente

    Amendes notables :

    → Amazon (Luxembourg) : 746M€ (2021) - publicité ciblée sans consentement

    → Instagram (Meta) : 405M€ (2022) - données enfants

    → Facebook (Meta) : 390M€ (2023) - base légale publicité

    → TikTok (Irlande) : 345M€ (2023) - données mineurs

    → Google France : 150M€ (2022) - refus de cookies difficile

    → Total 2023 (UE) : >2 milliards d'euros d'amendes

    Conclusion

    Le RGPD n'est pas qu'un obstacle légal — c'est une opportunité de construire la confiance avec vos utilisateurs. En pratique : minimisez la collecte, chiffrez les données sensibles, documentez vos traitements, et préparez vos procédures de réponse aux droits et aux violations. La CNIL propose des guides pratiques gratuits pour chaque secteur sur cnil.fr.

    💬 Voir l'article avec commentaires →
    ← CTF : les compétitions de hacking éthique Attaques supply chain : compromettre par les fournisseurs →