Zurück zur Startseite
Leistungen

Backend-Entwicklung, Security Engineering

Branche

B2B-Marktplatz

Jahr

2024

Verschlüsselung im Ruhezustand, ohne einen einzigen Download zu verändern

Die Rechtsabteilung eines B2B-Marktplatzes stellte eine Frage: Wenn jemand die Festplatte liest, was bekommt er? Die ehrliche Antwort war: alles. Tausende NDAs, Jahresabschlüsse und Unternehmensbewertungen lagen als normale Dateien in Djangos MEDIA_ROOT, lesbar für jeden mit einem geleakten SSH-Key oder einer Kopie des Backups. Unter der DSGVO war diese Antwort ein Haftungsrisiko.

DIE HERAUSFORDERUNG

Klartext in MEDIA_ROOT

Uploads waren normale Dateien im Media-Verzeichnis, benannt nach dem, was sie waren. Ein Angreifer brauchte nur Lesezugriff auf das Dateisystem, und dafür gab es drei plausible Wege: eine kompromittierte Abhängigkeit, einen geleakten SSH-Key oder ein Backup, das irgendwo landete, wo es nicht hingehörte. Jeder davon legte die Dokumente aller Kunden auf einmal offen. Es gab kein Zugriffs-Log, ein Einbruch hätte sich im Nachhinein nicht einmal beziffern lassen, und für das regulatorische Audit gab es keinen Nachweis für den Schutz ruhender Daten. Die Rechtsabteilung stufte das als kritisches DSGVO-Risiko ein. Die Produktseite war genauso fest: Am Hochladen und Herunterladen eines Dokuments durfte sich nichts ändern.

Dasselbe Verzeichnis, vorher und nachher

Was jemand mit Lesezugriff sieht. Zieh den Teiler: links die Auflistung, wie sie war, rechts dieselben Dateien nach der Verschlüsselungsschicht, Namen inklusive.

Klartext
Verschlüsselt
KlartextVerschlüsselt
DIE LÖSUNG

Fernet, entschlüsselt pro Anfrage

Die Bibliothek bot zwei Wege. Rohes AES-GCM ist schneller und authentifiziert in einem Primitiv, überlässt dir aber die Nonce, und eine einzige wiederverwendete Nonce unter demselben Schlüssel bricht das ganze Verfahren. Fernet ist das High-Level-Rezept derselben Bibliothek: AES-128-CBC plus HMAC-SHA256, ein zufälliger IV pro Token, ein Versionsbyte und ein Zeitstempel, in einer API, die sich schwer falsch benutzen lässt. Ich habe Fernet gewählt und dafür mit zwei Durchläufen über jede Datei und ohne Streaming bezahlt: Ein Dokument wird als Ganzes im Speicher entschlüsselt, für die Dauer einer HTTP-Antwort, und die Datei auf der Festplatte ist zu keinem Zeitpunkt etwas anderes als Chiffrat. Der Schlüssel wird aus einem Master-Secret mit PBKDF2-HMAC über 100.000 Iterationen abgeleitet und lebt nur in einer Umgebungsvariable, nie in der Datenbank, lässt sich also ohne Deployment rotieren.

Schlüsselableitung und Verschlüsselung. Das Master-Secret kommt aus der Umgebung; der abgeleitete Schlüssel ist der, den Fernet benutzt:

Python
import base64
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes

def get_fernet_key(master_secret: str) -> bytes:
    """Derive a Fernet key from the master secret via PBKDF2."""
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=b"stable-salt-from-settings",
        iterations=100_000,
    )
    return base64.urlsafe_b64encode(
        kdf.derive(master_secret.encode())
    )

def encrypt_file(file_bytes: bytes, key: bytes) -> bytes:
    """Encrypt raw file bytes with Fernet."""
    return Fernet(key).encrypt(file_bytes)

In einem Fernet-Token

Tipp einen kurzen Text ein und verschlüssle ihn. Der Token entsteht in deinem Browser mit WebCrypto, mit derselben Konstruktion wie auf dem Server: Version, Zeitstempel, IV, Chiffrat, HMAC. Dann entschlüssle und achte auf die Reihenfolge: Der HMAC wird zuerst geprüft, ein gekipptes Byte wird abgewiesen, bevor AES überhaupt läuft. Rotiere den Schlüssel, und der alte Token öffnet sich trotzdem, weil MultiFernet jeden Schlüssel probiert, den es hält.

Läuft in deinem Browser mit WebCrypto. Nichts verlässt diese Seite.

Version 0x80
1 Bytes
Zeitstempel
8 Bytes · 2024-10-27T14:10:00Z
IV
16 Bytes
Chiffrat
48 Bytes
HMAC-SHA256
32 Bytes

Token-Bytes nach Segment

Token, wie er auf der Festplatte liegt (base64url)

Entschlüsselungspfad

  1. HMAC über Version, Zeitstempel, IV und Chiffrat prüfen · wartet

Schlüsselring (MultiFernet)

  • Schlüssel #10f5e2476aktuell

Neue Tokens nutzen den ersten Schlüssel. Beim Entschlüsseln wird jeder Schlüssel der Reihe nach probiert, bis ein HMAC passt.

Token-Größe
105 B
Klartext
38 B
PKCS7-Padding
10 B
Overhead
67 B
Verschlüsselt mit
Schlüssel #1
Ergebnis
Bereit

Die ausliefernde View: Pfadprüfung, Schlüsselzugriff mit rollenspezifischen Fehlern, Entschlüsselung im Speicher, MIME aus den Bytes:

Python
class FetchCompanyDocumentsView(LoginRequiredMixin, View):

    def get(self, request, path):
        full_path = os.path.normpath(
            os.path.join(settings.MEDIA_ROOT, path)
        )

        # Path traversal protection
        if not full_path.startswith(settings.MEDIA_ROOT):
            raise SuspiciousFileOperation("Invalid path.")

        try:
            key = get_fernet_key(settings.ENCRYPTION_SECRET)
        except AttributeError:
            if request.user.is_staff:
                return HttpResponse("Encryption key not configured.", status=503)
            return HttpResponse("Please try again later.", status=503)

        encrypted = open(full_path, "rb").read()
        decrypted = Fernet(key).decrypt(encrypted)

        # Sniff MIME from decrypted content, not extension
        mime = magic.from_buffer(decrypted, mime=True)
        return HttpResponse(decrypted, content_type=mime)
DAS ERGEBNIS

Was das Audit zu sehen bekam

Für die Menschen auf der Plattform hat sich nichts geändert: dieselben Links, dieselben Dateinamen, dieselbe Geschwindigkeit. Geändert hat sich, was auf der Festplatte liegt, und was das Team dem Auditor vorlegen konnte. Die Frage, was passiert, wenn jemand die Festplatte liest, hat jetzt eine kurze Antwort, Chiffrat, und eine längere, die auf eine Seite passt, weil der ganze Mechanismus in einer Django-View und ihren Settings steckt.

Was sich dem Audit zeigen ließ

  • Algorithmus: Fernet, AES-128-CBC mit HMAC-SHA256-Authentifizierung, ein zufälliger IV pro Datei
  • Schlüsselableitung: PBKDF2-HMAC-SHA256, 100.000 Iterationen, aus einem Master-Secret, das nur in der Umgebung liegt
  • Schlüsselrotation: ein Management-Command verschlüsselt Datei für Datei neu, ohne Downtime und ohne Deployment
  • Zugriffspfad: eine login-geschützte View, Pfad normalisiert und auf MEDIA_ROOT begrenzt, Entschlüsselung nur im Speicher
  • Zugriffs-Logging: jede Dokumentanfrage wird protokolliert
  • Bilanz: über 10.000 ausgelieferte Dokumente, keine Entschlüsselungsfehler, unter 15 ms zusätzlich pro Download
KUNDENSTIMME

Als die Rechtsabteilung einen Nachweis für den Schutz ruhender Daten wollte, haben wir ihr noch am selben Nachmittag die View und die Schlüsselparameter geschickt. Im Produkt hat niemand die Änderung bemerkt; Downloads sehen aus und verhalten sich exakt wie vorher.

CTO

B2B-Marktplatz, Enterprise-Plattform

FÜR DEIN PROJEKT

  • Wann das passt

    Du speicherst Dokumente, die kein Fremder lesen darf, auf einem Server oder in Backups, an die andere herankommen, und ein Auditor könnte fragen, wie sie geschützt sind. Verschlüsselung in der Anwendung deckt Festplatte, Backup und die verirrte Kopie in einem Zug ab.

  • Was du prüfen solltest

    Wo Uploads heute liegen und wer diesen Pfad lesen kann: Deploy-User, Backup-Jobs, CI. Ob deine Downloads den Content-Type aus der Dateiendung ableiten; nach der Verschlüsselung geht das nicht mehr. Und wo ein Schlüssel liegen könnte, der weder Datenbank noch Repository ist.

  • Was es braucht

    Änderungen im Upload- und Download-Pfad, eine Umgebungsvariable oder ein Secret-Manager für den Master-Schlüssel, ein Rotations-Command und ein einmaliger Durchlauf über die Dateien, die schon auf der Festplatte liegen. Schreib die Parameter dorthin, wo die Rechtsabteilung sie findet; das ist die Audit-Antwort.

FAQ

TECHNOLOGIE-STACK

Django
Python
PostgreSQL

Seit mittlerweile 10 Jahren arbeiten wir immer wieder gerne mit Daniel zusammen. Wir schätzen dabei sehr seine schnellen Reaktionszeiten rund um die Uhr und sein Allroundwissen. Egal ob Serverkonfigurationen oder Programmierungen, er hat immer die passende Lösung.

Manuel Kasbarian - Geschäftsführer, SophistiX

Schließe dich Manuel an und starte dein nächstes Projekt mit Zuversicht.

Schreibe mirSchreibe mir