# STEP 4.2 – SICHERES PASSWORT-HASHING # Status: ABGESCHLOSSEN --- ## Ziel GAP #18: SHA-256 ohne Salt durch moderne KDF ersetzen. Transparente Migration bestehender Hashes ohne Forced Reset. ## Alte vs. neue Methode | Parameter | ALT (unsicher) | NEU (sicher) | |-----------|----------------|--------------| | Algorithmus | SHA-256 | bcrypt | | Salt | Keines | Automatisch (eingebettet) | | Cost-Faktor | n/a | 12 Runden | | Hash-Format | 64 Hex-Zeichen | $2b$12$... (60 Zeichen) | | Brute-Force-Resistenz | Minimal (GPU-optimiert) | ~250ms pro Versuch | | Rainbow-Table-Schutz | Keiner | Salt schützt | ## Parameter ``` bcrypt.gensalt(rounds=12) ``` - Cost: 12 (2^12 = 4096 Iterationen) - Salt: 16 Bytes, automatisch generiert - Output: 60-Zeichen-String mit eingebettetem Salt + Params - Format: $2b$12$<22-char-salt><31-char-hash> ## Geänderte Dateien | Datei | Zeile | Änderung | |-------|-------|----------| | basis14.py | 16-17 | `import bcrypt` hinzugefügt | | basis14.py | 1298-1300 | `_hash_password()`: SHA-256 → bcrypt | | basis14.py | 1302-1311 | NEU: `_verify_password()` mit Legacy-Erkennung | | basis14.py | 1313-1317 | NEU: `_is_legacy_hash()` | | basis14.py | 1355-1362 | Login: `_verify_password()` + automatischer Rehash | | basis14.py | 1459 | Registration: nutzt neuen `_hash_password()` (bcrypt) | | basis14.py | 1545 | Profil: `_verify_password()` für altes Passwort | | basis14.py | 1554 | Profil: `_hash_password()` für neues Passwort (bcrypt) | ## Migrationslogik ``` Login-Versuch: 1. _verify_password(pw, stored_hash) aufrufen 2. Prüfe: beginnt stored_hash mit "$2b$" oder "$2a$"? JA → bcrypt.checkpw() NEIN → SHA-256 Legacy-Vergleich 3. Bei erfolgreicher Anmeldung: _is_legacy_hash(stored_hash)? JA → Rehash mit bcrypt, Profil speichern NEIN → Nichts tun ``` - Kein Forced Reset - Alter Hash wird beim nächsten Login automatisch ersetzt - Neuer Hash sofort bei Registration und Passwortänderung ## Legacy-Erkennung Ein Hash ist Legacy (SHA-256) wenn: - Er NICHT mit "$2" beginnt (kein bcrypt-Prefix) - Er exakt 64 Zeichen lang ist (SHA-256 Hex-Output) ## Tests | Test | Ergebnis | |------|----------| | Neuer bcrypt-Hash erstellen | PASS ($2b$12$...) | | bcrypt verify (korrektes PW) | PASS (True) | | bcrypt verify (falsches PW) | PASS (False) | | Legacy SHA-256 erkennen | PASS (is_legacy=True) | | bcrypt als nicht-Legacy erkennen | PASS (is_legacy=False) | | Legacy verify (korrektes PW) | PASS (True) | | Legacy verify (falsches PW) | PASS (False/rejected) | | Legacy rehash zu bcrypt | PASS (verify nach rehash True) | ## Nicht geändert - workforce_planner/api/auth.py – nutzt bereits bcrypt (cost=default) - aza_persistence.py – importiert hashlib, nutzt es aber nicht für Passwörter - Backup-Kopien (basis14 - Kopie*.py) – bewusst nicht geändert ## Rest-Risiken 1. **bcrypt als Dependency**: Muss installiert sein (`pip install bcrypt`). Ist bereits Dependency von workforce_planner. 2. **Timing**: Legacy-SHA-256-Vergleich ist schneller als bcrypt. Ein Timing-Seitenkanalangriff könnte erkennen, ob ein Nutzer noch einen alten Hash hat. Risiko: minimal (lokale Desktop-App). 3. **workforce_planner cost**: Nutzt bcrypt mit Default-Rounds (10). Empfehlung: Auf 12 angleichen (separater Schritt).