3.3 KiB
3.3 KiB
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
-
bcrypt als Dependency: Muss installiert sein (
pip install bcrypt). Ist bereits Dependency von workforce_planner. -
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).
-
workforce_planner cost: Nutzt bcrypt mit Default-Rounds (10). Empfehlung: Auf 12 angleichen (separater Schritt).