100 lines
3.3 KiB
Markdown
100 lines
3.3 KiB
Markdown
|
|
# 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).
|