Aller au contenu principal

Chiffrement des Données

Vue d'ensemble​

MyElectricalData implémente un système de chiffrement GDPR-compliant pour protéger les données sensibles des utilisateurs (consommation, production, contrats). Chaque utilisateur possède une clé de chiffrement unique dérivée de son client_secret.

┌─────────────────────────────────────────────────────────────┐
│ FLUX DE CHIFFREMENT │
├─────────────────────────────────────────────────────────────┤
│ │
│ Utilisateur │
│ │ │
│ ▼ │
│ Authentification (JWT ou client_secret) │
│ │ │
│ ▼ │
│ Récupération du client_secret │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Dérivation de clé │ │
│ │ │ │
│ │ 1. SHA256(client_secret) │ │
│ │ 2. Base64 URL-safe encode │ │
│ │ 3. Création cipher Fernet │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ Valkey (données chiffrées) │ │
│ │ │ │
│ │ consumption:daily:{pdl}:{date} │ │
│ │ production:daily:{pdl}:{date} │ │
│ │ contract:{pdl} │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

Algorithme : Fernet​

Le système utilise Fernet de la bibliothèque cryptography Python :

  • AES-128-CBC pour le chiffrement
  • HMAC-SHA256 pour l'authentification
  • Timestamps pour la validation temporelle
AvantageDescription
Sécurisé par défautPas de configuration complexe, résistant aux attaques courantes
AuthentifiéHMAC garantit l'intégrité des données
SimpleAPI minimaliste, moins de risques d'erreur
StandardUtilisé largement dans l'écosystème Python

Implémentation​

Fichier principal​

apps/api/src/services/cache.py

Dérivation de la clé​

def _get_cipher(self, encryption_key: str) -> Fernet:
"""Get Fernet cipher with user's client_secret as key"""
from base64 import urlsafe_b64encode
from hashlib import sha256

key = urlsafe_b64encode(sha256(encryption_key.encode()).digest())
return Fernet(key)

Chiffrement en cache​

async def set(self, key: str, value: Any, encryption_key: str, ttl: int | None = None) -> bool:
"""Store encrypted data in Valkey"""
json_data = json.dumps(value)
cipher = self._get_cipher(encryption_key)
encrypted_data = cipher.encrypt(json_data.encode())
await self.redis.setex(key, ttl or self.default_ttl, encrypted_data)
return True

async def get(self, key: str, encryption_key: str) -> Any | None:
"""Retrieve and decrypt data from Valkey"""
encrypted_data = await self.redis.get(key)
if not encrypted_data:
return None
cipher = self._get_cipher(encryption_key)
decrypted_data = cipher.decrypt(encrypted_data)
return json.loads(decrypted_data.decode())

Clés de cache​

Type de donnéesClé de cache
Consommation journalièreconsumption:daily:12345678901234:2024-01-15
Production journalièreproduction:daily:12345678901234:2024-01-15
Contratcontract:12345678901234
Adresseaddress:12345678901234

Propriétés de sécurité​

PropriétéDescription
Isolation utilisateurChaque cache est chiffré avec un secret unique
ConfidentialitéImpossible de déchiffrer sans le bon client_secret
IntégritéHMAC-SHA256 détecte toute modification
GDPRDonnées personnelles chiffrées au repos
TTL automatiqueExpiration après 24h (configurable)

Références​