Passwort Hashing Sicher
Passwort-Hashing: Sichere Speicherung
Passwörter dürfen NIEMALS im Klartext gespeichert werden. Dieser Guide erklärt, wie Sie Passwörter sicher hashen und welche Algorithmen Sie verwenden sollten.
Warum Hashing?
Bei einem Datenleck sind gehashte Passwörter nutzlos für Angreifer – sie können nicht zurückgerechnet werden.
Klartext: "geheim123" MD5 (unsicher): e99a18c428cb38d5f260853678922e03 bcrypt (sicher): $2y$10$X4kv7j5ZcQdx8.9sFqK...
Sichere Algorithmen
| Algorithmus | Empfehlung | Anmerkung |
|---|---|---|
| Argon2id | ⭐⭐⭐ Beste Wahl | Gewinner PHC, memory-hard |
| bcrypt | ⭐⭐⭐ Sehr gut | Bewährt, weit verbreitet |
| scrypt | ⭐⭐ Gut | Memory-hard |
| PBKDF2 | ⭐ Akzeptabel | Wenn nichts anderes verfügbar |
❌ NIEMALS verwenden:
- MD5: Zu schnell, Kollisionen
- SHA1: Zu schnell, gebrochen
- SHA256 ohne Salt/Iteration: Rainbow Tables
- Eigene Algorithmen: Nicht getestet
PHP: password_hash()
<?php
// ✅ EMPFOHLEN: bcrypt (Standard)
$hash = password_hash($password, PASSWORD_DEFAULT);
// Argon2id (PHP 7.3+)
$hash = password_hash($password, PASSWORD_ARGON2ID);
// Mit Optionen
$hash = password_hash($password, PASSWORD_BCRYPT, [
'cost' => 12 // Standard: 10
]);
$hash = password_hash($password, PASSWORD_ARGON2ID, [
'memory_cost' => 65536, // 64 MB
'time_cost' => 4,
'threads' => 3
]);
Passwort verifizieren
<?php
// Passwort prüfen
if (password_verify($inputPassword, $storedHash)) {
echo "Passwort korrekt!";
// Rehash wenn nötig (nach Algorithmus-Update)
if (password_needs_rehash($storedHash, PASSWORD_DEFAULT)) {
$newHash = password_hash($inputPassword, PASSWORD_DEFAULT);
// In Datenbank speichern
}
} else {
echo "Passwort falsch!";
}
Node.js: bcrypt
const bcrypt = require('bcrypt');
// Passwort hashen
const saltRounds = 12;
const hash = await bcrypt.hash(password, saltRounds);
// Passwort verifizieren
const isMatch = await bcrypt.compare(inputPassword, storedHash);
if (isMatch) {
console.log('Passwort korrekt!');
}
Mit Argon2
const argon2 = require('argon2');
// Hashen
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536,
timeCost: 3,
parallelism: 4
});
// Verifizieren
const isValid = await argon2.verify(storedHash, inputPassword);
Python: passlib oder bcrypt
# Mit passlib (empfohlen)
from passlib.hash import argon2
# Hashen
hash = argon2.hash(password)
# Verifizieren
if argon2.verify(input_password, stored_hash):
print("Passwort korrekt!")
# Mit bcrypt
import bcrypt
# Hashen
salt = bcrypt.gensalt(rounds=12)
hash = bcrypt.hashpw(password.encode(), salt)
# Verifizieren
if bcrypt.checkpw(input_password.encode(), stored_hash):
print("Passwort korrekt!")
Wie funktioniert bcrypt?
$2y$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW └─┬┘└┬┘└────────────────────┬────────────────────┘└──────────┬──────────┘ │ │ Salt (22 Zeichen) Hash (31 Zeichen) │ └── Cost Factor (2^12 = 4096 Iterationen) └── Algorithmus Version (2y = bcrypt)
- Salt: Zufällig, automatisch generiert, im Hash enthalten
- Cost Factor: Exponentiell mehr Rechenzeit
- Result: 60 Zeichen String
Cost Factor / Work Factor
| Cost | Iterationen | Zeit (ca.) |
|---|---|---|
| 10 | 1.024 | ~100ms |
| 12 | 4.096 | ~300ms |
| 14 | 16.384 | ~1s |
Ziel: 250-500ms pro Hash. Zu schnell = unsicher, zu langsam = schlechte UX.
Migration von MD5/SHA
<?php
// Bei Login alte Hashes migrieren
function loginUser($username, $password) {
$user = getUserFromDb($username);
// Alter MD5-Hash?
if (strlen($user['password_hash']) === 32) {
if (md5($password) === $user['password_hash']) {
// Neu hashen und speichern!
$newHash = password_hash($password, PASSWORD_DEFAULT);
updateUserPassword($user['id'], $newHash);
return true;
}
}
// Neuer bcrypt-Hash
return password_verify($password, $user['password_hash']);
}
Best Practices
💡 Empfehlungen:
- Argon2id oder bcrypt verwenden
- Eingebaute Funktionen nutzen (nicht selbst implementieren)
- Cost Factor regelmäßig erhöhen
- password_needs_rehash() nutzen
- Passwort-Länge auf 72 Bytes begrenzen (bcrypt-Limit)
- Pepper optional (zusätzlicher Secret Key)