Initial commit
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user