Files
aza/AzA march 2026/security/handovers/STEP_4_2_HASHING.md
2026-03-25 22:03:39 +01:00

3.3 KiB
Raw Blame History

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).