Stocker en toute sécurité les clés secrètes dans un modèle Django (version Fernet)
La sécurité est la responsabilité du développeur – il ne faut pas stocker les valeurs en clair dans la base de données.
1. Pourquoi le chiffrement est-il nécessaire ?
- API Key / Secret / OAuth token : une fuite unique équivaut à ouvrir la porte à l’ensemble du service.
- Si vous les hardcodez dans
settings.py, ils peuvent être exposés dans le dépôt Git, les artefacts de déploiement ou sur votre machine de développement. - Même si vous les placez dans des variables d’environnement, les enregistrer tel quel dans la base de données signifie que dès que la BDD est compromise, les secrets apparaissent en clair.
Un point important à retenir :
- Mot de passe
- Le serveur n’a pas besoin de connaître le texte en clair → hash (BCrypt, Argon2)
- API Key / Secret / Token
- Le serveur doit pouvoir les réutiliser → chiffrement déchiffrable
Résumé
- Données sensibles à réutiliser → chiffrer avant de stocker
- Données à ne pas réutiliser (mot de passe, etc.) → hasher avant de stocker
2. Qu’est‑ce que Fernet ?
cryptography.fernet.Fernet est une API de haut niveau qui regroupe tout en une seule fois.
- Utilise des algorithmes sûrs en interne (AES + HMAC, etc.)
- Gère automatiquement IV aléatoire, horodatage, vérification d’intégrité (HMAC)
- Le développeur n’a qu’à conserver une clé
encrypt(texte)→ chaîne de tokendecrypt(token)→ texte clair
En d’autres termes, vous n’avez pas à vous soucier d’AES‑CBC/IV/padding/HMAC :
from cryptography.fernet import Fernet
key = Fernet.generate_key() # Génération de la clé
f = Fernet(key)
token = f.encrypt(b"hello") # Chiffrement
plain = f.decrypt(token) # Déchiffrement
Comprendre ces quelques lignes suffit pour l’utiliser.
Ce qu’il faut retenir
- Gestion de la clé (variables d’environnement, Secret Manager, etc.)
- Faire en sorte que Django chiffre/déchiffre automatiquement lors des écritures/lectures
3. Génération de la clé Fernet et configuration Django
3‑1. Générer la clé Fernet
# Générer une clé Fernet (sous forme de chaîne Base64)
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Exemple de sortie :
twwhe-3rGfW92V5rvvW3o4Jhr0rVg7x8lQM6zCj6lTY=
Placez cette chaîne dans une variable d’environnement de votre serveur.
export DJANGO_FERNET_KEY="twwhe-3rGfW92V5rvvW3o4Jhr0rVg7x8lQM6zCj6lTY="
3‑2. Préparer une instance Fernet dans settings.py
# settings.py
import os
from cryptography.fernet import Fernet
DJANGO_FERNET_KEY = os.environ["DJANGO_FERNET_KEY"] # Chaîne
FERNET = Fernet(DJANGO_FERNET_KEY.encode("utf-8"))
Vous pouvez désormais appeler settings.FERNET.encrypt(...) ou settings.FERNET.decrypt(...) depuis n’importe quel module Django.
4. Ajouter un champ chiffré Fernet à un modèle Django
Au lieu d’implémenter un chiffrement complexe, il suffit de créer un champ personnalisé très simple.
4‑1. Points de conception
- La base de données ne stocke que le token chiffré
- Le code modèle, vue ou service ne voit que le texte clair
- Le motif
_secret+secretsépare la colonne de base de données de la logique métier
4‑2. Implémentation de EncryptedTextField
# myapp/utils/encrypted_field.py
from django.db import models
from django.conf import settings
class EncryptedTextField(models.TextField):
"""Champ TextField chiffré basé sur Fernet."""
description = "Fernet encrypted text field"
def get_prep_value(self, value):
"""Python -> valeur stockée en BDD."""
if value is None:
return None
if isinstance(value, str):
if value == "":
return ""
token = settings.FERNET.encrypt(value.encode("utf-8"))
return token.decode("utf-8")
return value
def from_db_value(self, value, expression, connection):
"""Valeur BDD -> Python."""
if value is None:
return None
try:
token = value.encode("utf-8")
decrypted = settings.FERNET.decrypt(token)
return decrypted.decode("utf-8")
except Exception:
return value
def to_python(self, value):
return value
Astuce de production
Si votre base contient déjà des données en clair, vous pouvez laisser
from_db_valuerenvoyer la valeur brute en cas d’échec de déchiffrement, puis les réécrire chiffrées lors de la prochaine sauvegarde.
5. Appliquer le champ au modèle
# myapp/models.py
from django.db import models
from .utils.encrypted_field import EncryptedTextField
class MyModel(models.Model):
name = models.CharField(max_length=100)
_secret = EncryptedTextField() # Colonne réelle (token Fernet)
@property
def secret(self):
"""Accès en clair depuis l’extérieur."""
return self._secret
@secret.setter
def secret(self, value: str):
"""Assigne un texte clair, qui sera chiffré automatiquement."""
self._secret = value
_secret: nom réel de la colonne BDD, contenant le token chiffré.secret: champ logique utilisé dans le code, toujours en clair.
Ainsi, un développeur peut faire :
obj.secret # texte clair
obj._secret # valeur chiffrée stockée
6. Exemples d’utilisation
6‑1. Dans la console Django
>>> from myapp.models import MyModel
# Créer un nouvel objet
>>> obj = MyModel(name="Test")
>>> obj.secret = "my-very-secret-key" # assignation en clair
>>> obj.save()
# La colonne réelle contient un token chiffré
>>> MyModel.objects.get(id=obj.id)._secret
'gAAAAABm...4xO8Uu1f0L3K0Ty6fX5y6Zt3_k6D7eE-'
# Accès en clair via le modèle
>>> obj.secret
'my-very-secret-key'
6‑2. Dans une API
En pratique, on ne renvoie pas le secret au client, mais voici un exemple simplifié :
# myapp/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import MyModel
class SecretView(APIView):
def get(self, request, pk):
obj = MyModel.objects.get(pk=pk)
return Response({"secret": obj.secret})
Dans un environnement réel, on utiliserait obj.secret uniquement pour appeler d’autres services internes, sans jamais l’exposer au client.
7. Points à connaître lorsqu’on utilise Fernet
7‑1. Recherche / filtrage
Fernet produit un résultat différent à chaque chiffrement. Ainsi, même si deux secrets sont identiques, leurs tokens chiffrés seront différents.
obj1.secret = "abc"
obj2.secret = "abc"
Les requêtes suivantes ne fonctionneront pas :
MyModel.objects.filter(_secret="abc") # sans sens
MyModel.objects.filter(_secret=obj._secret) # comparaison de tokens, pas de texte clair
Si vous avez besoin de rechercher ou trier par texte clair, envisagez :
- une colonne de recherche (hash, préfixe, etc.)
- ou repensez la conception pour éviter de chiffrer ces champs.
7‑2. Rotation de clé
Il est préférable de ne pas garder la même clé indéfiniment.
- Commencez avec une seule clé
DJANGO_FERNET_KEY. - Plus tard, introduisez
OLD_KEYetNEW_KEY: - Chiffrement : toujours avec
NEW_KEY. - Déchiffrement : essayer
OLD_KEYpuisNEW_KEY. - Au fil du temps, les données seront ré‑chiffrées avec
NEW_KEYetOLD_KEYpourra être supprimée.
8. Checklist de sécurité (version Fernet)
| Élément | Vérification |
|---|---|
| Gestion de la clé | Ne pas versionner DJANGO_FERNET_KEY; charger depuis Secrets Manager, Parameter Store, Vault, etc. |
| Séparation des environnements | Injecter la clé uniquement sur les serveurs de production; utiliser une clé différente en dev/local |
| Chiffrement ciblé | Chiffrer uniquement les valeurs réellement sensibles (API Key, Secret, Token) ; laisser le reste en clair |
| Logs | Ne jamais enregistrer le texte clair, le token ou la clé dans les logs (surtout en DEBUG) |
| Sauvegardes | Même si la BDD est sauvegardée, sans la clé les données restent inutilisables |
| Tests | Vérifier que encrypt -> decrypt donne le même résultat ; tester la migration de données en clair |
9. Conclusion
Implémenter un chiffrement bas niveau est à la fois complexe et source d’erreurs. En utilisant Fernet, vous bénéficiez :
- d’une combinaison d’algorithmes sécurisés
- d’un IV aléatoire, d’un horodatage et d’une vérification d’intégrité
- d’une API simple
Dans Django, un champ personnalisé comme EncryptedTextField couplé au motif _secret / secret permet aux développeurs de travailler uniquement avec du texte clair, tout en garantissant que la base de données ne contient que des valeurs chiffrées.
En résumé
- Tout secret sensible doit être chiffré avant d’être stocké.
- Laissez la bibliothèque gérer le chiffrement. En suivant ces deux principes, la sécurité de votre service Django s’améliorera considérablement. 🔐

Aucun commentaire.