Sichere Speicherung von Geheimschlüsseln in Django-Modellen (Fernet-Version)
Sicherheit ist die Verantwortung des Entwicklers – Rohdaten sollten nicht im Klartext in der Datenbank abgelegt werden.
1. Warum ist Verschlüsselung nötig?
- API‑Keys / Secrets / OAuth‑Tokens sind bei einem einzigen Leck gleichbedeutend mit einem kompletten Durchbruch des Dienstes.
- Werden sie in
settings.pyhardcodiert, → Git‑Repository, Deploy‑Artefakte, Entwickler‑Laptops exponieren sie direkt. - Auch wenn sie in Umgebungsvariablen liegen, → Werden sie in die Datenbank geschrieben, ist das Datenbank‑Leck gleichbedeutend mit Klartext‑Exposition.
Ein weiterer wichtiger Punkt:
- Passwörter
- Der Server muss den Klartext nicht kennen → Hashing (BCrypt, Argon2)
- API‑Keys / Secrets / Tokens
- Der Server muss den Klartext wiederverwenden → Verschlüsselung, die entschlüsselt werden kann
Zusammenfassung
- Sensibler Daten, die wiederverwendet werden → verschlüsselt speichern
- Daten, die nicht wiederverwendet werden (Passwörter etc.) → hashen
2. Was ist Fernet?
cryptography.fernet.Fernet ist eine hoch‑abstrakte API, die alles in einem Schritt bietet.
- Nutzt intern sichere Algorithmen (AES + HMAC etc.)
- Zufällige IV, Zeitstempel, Integritätsprüfung (HMAC) werden automatisch gehandhabt
- Der Entwickler muss nur einen Schlüssel sicher aufbewahren und
encrypt(Plaintext)→ Token‑Stringdecrypt(Token)→ Klartext- Aufrufen reicht
from cryptography.fernet import Fernet
key = Fernet.generate_key() # Schlüssel erzeugen
f = Fernet(key)
token = f.encrypt(b"hello") # verschlüsseln
plain = f.decrypt(token) # entschlüsseln
Das reicht, um es in der Praxis zu nutzen.
Was wir beachten müssen:
- Schlüsselverwaltung (Umgebungsvariable, Secret Manager etc.)
- In Django automatisches encrypt/decrypt beim Schreiben/Lesen
3. Fernet‑Schlüssel erzeugen und Django konfigurieren
3‑1. Fernet‑Schlüssel erzeugen
# Einen Fernet‑Schlüssel erzeugen (Base64‑String)
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Beispielausgabe:
twwhe-3rGfW92V5rvvW3o4Jhr0rVg7x8lQM6zCj6lTY=
Speichere diesen String in einer Umgebungsvariable.
export DJANGO_FERNET_KEY="twwhe-3rGfW92V5rvvW3o4Jhr0rVg7x8lQM6zCj6lTY="
3‑2. Fernet‑Instanz in settings.py vorbereiten
# settings.py
import os
from cryptography.fernet import Fernet
DJANGO_FERNET_KEY = os.environ["DJANGO_FERNET_KEY"] # String
FERNET = Fernet(DJANGO_FERNET_KEY.encode("utf-8"))
Jetzt kann überall in Django from django.conf import settings und settings.FERNET.encrypt(...), settings.FERNET.decrypt(...) verwendet werden.
4. Fernet‑verschlüsselte Felder in Django‑Modellen
Anstatt komplexe Block‑Verschlüsselung zu implementieren, reicht ein einfaches, benutzerdefiniertes Feld.
4‑1. Design‑Punkte
- In der Datenbank wird nur der verschlüsselte Token‑String gespeichert
- Im Code arbeitet man immer mit dem entschlüsselten Klartext
- Das Muster
_secret+secrettrennt die DB‑Spalte von der logischen Bedeutung
4‑2. EncryptedTextField implementieren
# myapp/utils/encrypted_field.py
from django.db import models
from django.conf import settings
class EncryptedTextField(models.TextField):
"""Fernet‑basierte verschlüsselte TextField.
- Speichern: Klartext(str) → Fernet‑Token(str)
- Laden: Fernet‑Token(str) → Klartext(str)
"""
description = "Fernet verschlüsseltes Textfeld"
def get_prep_value(self, value):
"""Python‑Objekt → DB‑Wert"""
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):
"""DB‑Wert → Python‑Objekt"""
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):
"""ORM‑Python‑Objekt"""
return value
Praxis‑Tipp
- Falls alte Daten noch im Klartext liegen, kann
from_db_valuebei einem Decrypt‑Fehler den Klartext zurückgeben und später beim Speichern automatisch verschlüsselt werden.
5. Im Modell einsetzen
# 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() # DB‑Spalte (Fernet‑Token)
@property
def secret(self):
"""Externe Nutzung – immer Klartext"""
return self._secret
@secret.setter
def secret(self, value: str):
"""Zuweisung – automatisch verschlüsselt"""
self._secret = value
_secret– tatsächliche DB‑Spaltesecret– logisches Feld im Code, immer Klartext
So bleibt die Trennung klar, und Entwickler sehen sofort, welche Spalte verschlüsselt ist.
6. Beispiel‑Anwendung
6‑1. Django‑Shell
>>> from myapp.models import MyModel
# Objekt erstellen
>>> obj = MyModel(name="Test")
>>> obj.secret = "my-very-secret-key" # Klartext zuweisen
>>> obj.save()
# In der DB gespeicherte Spalte
>>> MyModel.objects.get(id=obj.id)._secret
'gAAAAABm...4xO8Uu1f0L3K0Ty6fX5y6Zt3_k6D7eE-'
# Im Code
>>> obj.secret
'my-very-secret-key'
6‑2. In einer API nutzen
# 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})
In der Praxis wird obj.secret meist nur intern für Aufrufe verwendet und nicht an den Client weitergegeben.
7. Wichtige Punkte bei Fernet
7‑1. Einschränkungen bei Suche/Filter
Fernet erzeugt bei jedem Aufruf einen anderen Token.
obj1.secret = "abc"
obj2.secret = "abc"
Obwohl beide Klartexte gleich sind, unterscheiden sich die gespeicherten Tokens.
Daher funktionieren folgende Abfragen nicht:
MyModel.objects.filter(_secret="abc") # Sinnlos
MyModel.objects.filter(_secret=obj._secret) # Nur Token‑Vergleich möglich
Wenn du nach Klartext filtern musst, brauchst du entweder * eine zusätzliche Spalte (Hash, Präfix) oder * die Verschlüsselung überdenken.
7‑2. Schlüsselrotation
Ein einzelner Schlüssel sollte nicht ewig verwendet werden.
- Start: Nur
DJANGO_FERNET_KEY - Weiter:
OLD_KEY,NEW_KEY - Verschlüsseln: nur NEW_KEY
- Entschlüsseln: beide versuchen
- Daten werden schrittweise auf NEW_KEY migriert
8. Sicherheits‑Checkliste (Fernet)
| Punkt | Check |
|---|---|
| Schlüsselverwaltung | DJANGO_FERNET_KEY nicht im Git, sondern aus Secrets‑Manager, Parameter Store oder Vault laden |
| Zugriffs‑Trennung | Nur Produktionsserver erhalten den Schlüssel, Entwickler/Local nutzen andere Schlüssel |
| Verschlüsselungs‑Ziel | Nur wirklich sensible Werte (API‑Keys, Tokens) verschlüsseln, andere als normale Spalten belassen |
| Logging | Keine Klartexte, Tokens oder Schlüssel in Logs, besonders bei DEBUG |
| Backups | Ohne Schlüssel sind Backups nutzlos – stelle sicher, dass Schlüssel gesichert sind |
| Tests | Unit‑Tests für encrypt → decrypt und Migration von Klartextdaten |
9. Fazit
- Niedrig‑level AES‑Implementierungen sind fehleranfällig.
- Mit Fernet erhältst du:
- Sichere Algorithmen‑Kombination
- Automatisches IV, Zeitstempel, Integritätsprüfung
- Einfache API
- In Django kannst du mit
EncryptedTextFieldund dem_secret/secret‑Pattern - Entwickler arbeiten mit Klartext, die DB speichert immer verschlüsselt.
Schlussfolgerung
- Alle sensiblen Daten verschlüsseln
- Komplexe Algorithmen delegieren an bewährte Bibliotheken – so steigt die Sicherheit deiner Django‑Anwendung signifikant.

Es sind keine Kommentare vorhanden.