Files
aza/AzA march 2026/security/handovers/STEP_4_2_HASHING.md

100 lines
3.3 KiB
Markdown
Raw Normal View History

2026-03-25 22:03:39 +01:00
# 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).