Passwörter
Ein Passwort zu speichern bedeutet, etwas zu speichern, das ein Angreifer selbst
mit Ihrer gesamten Datenbank in der Hand nicht umkehren kann. Rustango liefert
Ihnen das in zwei Aufrufen — hash beim Eingang, verify beim Ausgang —
gestützt auf argon2id, den memory-hard Gewinner der Password Hashing
Competition und die aktuelle Erstwahl der OWASP. Sie speichern, protokollieren
oder vergleichen den Klartext niemals.
Quelle:
rustango::passwords(hash,verify,verify_dummy,strength_score,StrengthIssue) — hinter dem Featurepasswords(standardmäßig aktiviert). Für die in die Mandantenfähigkeit integrierten Passwort-Hilfsfunktionen sieherustango::tenancy::password.Ausführbare Version: jeder Ausschnitt unten ist aus dem getesteten Beispiel
auth_demokopiert —cargo test -p auth_demo --test auth_passwords.
Ein Begriff hier neu? hash, salt, argon2id, PHC-Zeichenkette — siehe das Glossar.
Dies ist die Vertiefung zum Abschnitt „Passwörter hashen und prüfen" des Sicherheitsleitfadens.
Inhaltsverzeichnis
- Schnellstart · Warum argon2id
- Hashen bei der Registrierung · Prüfen bei der Anmeldung
- Timing-sichere Anmeldungen · Stärkeprüfungen
- Wo der Hash lebt · Hinweise und Grenzen
Schnellstart
use rustango::passwords::{hash, verify};
// Signup — store the returned PHC string, never the plaintext.
let stored: String = hash("CorrectHorseBatteryStaple!42")?;
// Login — check an attempt against the stored hash.
if verify("CorrectHorseBatteryStaple!42", &stored)? {
// credentials good
}
hash gibt eine PHC-Zeichenkette
zurück — eine selbstbeschreibende Zeile, die den Algorithmus, seine
Kostenparameter, das zufällige Salt und den Digest trägt:
$argon2id$v=19$m=19456,t=2,p=1$<base64 salt>$<base64 hash>
Da Salt und Parameter innerhalb der Zeichenkette mitreisen, benötigt verify
nur den gespeicherten Wert und den Versuch — es gibt keine separate Salt-Spalte
zu verwalten.
Warum argon2id
hash verwendet argon2id mit den von der OWASP empfohlenen Standardwerten
(m=19 MiB, t=2, p=1). argon2id ist memory-hard: jeder Rateversuch kostet echten
RAM, und genau das stumpft die GPU/ASIC-Farmen ab, die schnelle Hashes (MD5,
SHA-256, sogar bcrypt bei niedrigen Kosten) per Brute-Force angreifbar machen.
Zwei Eigenschaften sind für die Korrektheit wichtig:
-
Das Salting ist automatisch und pro Hash. Dasselbe Passwort zweimal zu hashen ergibt zwei verschiedene PHC-Zeichenketten, sodass identische Passwörter in Ihrer Tabelle nicht kollidieren und Angriffe mit vorberechneten Rainbow-Tables nicht greifen.
let a = hash("same-password-12345")?; let b = hash("same-password-12345")?; assert_ne!(a, b); // different random salt each time assert!(verify("same-password-12345", &a)?); assert!(verify("same-password-12345", &b)?); -
Die Prüfung erfolgt in konstanter Zeit beim Digest-Vergleich (argon2s eigener
PasswordVerifier), sodass ein Byte-für-Byte-Timing-Leak nicht verraten kann, wie viel eines Versuchs richtig war.
Hashen bei der Registrierung
use rustango::passwords::{hash, strength_score};
fn create_user(username: &str, plaintext: &str) -> Result<String, String> {
// Optional: nudge users away from weak choices (see below).
let issues = strength_score(plaintext);
if !issues.is_empty() {
return Err(format!("password too weak: {issues:?}"));
}
// Store the PHC string on the user row (e.g. auth_users.password_hash).
hash(plaintext).map_err(|e| e.to_string())
}
Prüfen bei der Anmeldung
use rustango::passwords::verify;
// `stored` is the PHC string you saved at signup.
let ok = verify(attempt, &stored)?;
verify gibt zurück:
Ok(true)— der Versuch stimmt überein.Ok(false)— er stimmt nicht.Err(PasswordError::Verify)—storedwar keine gültige PHC-Zeichenkette (eine beschädigte oder abgeschnittene Spalte), behandeln Sie es also als fehlgeschlagene Anmeldung, nicht als 500.
Timing-sichere Anmeldungen (Kontoaufzählung)
Wenn Ihre Anmeldung das teure verify nur ausführt, wenn der Benutzername
existiert, kehrt ein unbekannter Benutzername merklich schneller zurück als ein
echter — und diese Zeitlücke lässt einen Angreifer gültige Konten aufzählen.
verify_dummy schließt sie: rufen Sie es im Zweig Benutzer-nicht-gefunden (und
Konto-inaktiv) auf, damit jede Anmeldung unabhängig davon die Arbeit eines
argon2-verify aufwendet.
use rustango::passwords::{verify, verify_dummy};
let row = users::find_by_username(username).await?;
let authenticated = match row {
Some(u) if u.is_active => verify(attempt, &u.password_hash)?,
_ => {
verify_dummy(attempt); // burn the same work, then fail
false
}
};
Stärkeprüfungen
strength_score gibt einen Vec<StrengthIssue> zurück — leer bedeutet „gut
genug". Es ist eine bewusst leichtgewichtige Heuristik, um Benutzer zu
ermutigen, kein hartes Richtlinien-Gatter; kombinieren Sie sie für ernsthafte
Deployments mit einer Prüfung gegen eine Leak-Liste (HIBP / pwned-passwords).
use rustango::passwords::{strength_score, StrengthIssue};
assert!(strength_score("Tr0ub4dor&3-CorrectBattery").is_empty());
assert!(strength_score("password123").contains(&StrengthIssue::KnownWeak));
assert!(strength_score("short").contains(&StrengthIssue::TooShort));
StrengthIssue | Ausgelöst, wenn |
|---|---|
TooShort | weniger als 12 Zeichen |
NoDigitsOrSymbols | nur Buchstaben — keine Ziffer oder kein Symbol |
NoVariety | nur Kleinbuchstaben |
KnownWeak | stimmt mit der kleinen eingebauten Liste schwacher Passwörter überein (ohne Berücksichtigung der Groß-/Kleinschreibung) |
Wo der Hash lebt
Die PHC-Zeichenkette ist lediglich eine String-Spalte auf dem beliebigen
Kontomodell, das Ihnen gehört. Im Beispiel
auth_demo:
#[derive(Model, Clone, Debug)]
#[rustango(table = "auth_users", display = "username")]
pub struct User {
#[rustango(primary_key)]
pub id: Auto<i64>,
#[rustango(max_length = 150, unique)]
pub username: String,
#[rustango(max_length = 254)]
pub email: String,
#[rustango(max_length = 255)] // PHC strings are ~95 chars at these params
pub password_hash: String,
pub is_active: bool,
pub is_superuser: bool,
}
Sobald der Benutzer authentifiziert ist, übergeben Sie an eine Session (für Browser-Anwendungen) oder stellen Sie ein JWT aus (für APIs).
Hinweise und Grenzen
- Niemals den Klartext speichern, protokollieren oder mit
==vergleichen.hash→ speichern;verify→ prüfen. Das ist der ganze Vertrag. - Die Kostenparameter sind die OWASP-Standardwerte, fest eingebaut. Sie sind eine vernünftige Untergrenze; sie später anzuheben ist sicher — alte Hashes verifizieren weiterhin (ihre Parameter leben in der PHC-Zeichenkette), und Sie können bei der nächsten erfolgreichen Anmeldung neu hashen, um sie zu aktualisieren.
strength_scoreist eine Heuristik, keine Richtlinien-Engine — es wirdSummer2024!nicht erkennen. Legen Sie für echte Stärkedurchsetzung eine Leak-Listen-Abfrage darüber.- Für mandantenfähige Anwendungen mit dem Benutzerspeicher des Frameworks
bevorzugen Sie
rustango::tenancy::password(dasselbe argon2id, integriert in das Benutzermodell des Mandanten). Dieses Modul ist die eigenständige Version für Anwendungen, die ihre eigene User-Tabelle besitzen.
