Il problema che si verifica è che se l'hash della password è tutto ciò che è necessario per generare un token valido, un utente malintenzionato con un dump del database può generare token di reimpostazione per tutti (che tipo di sconfigge lo scopo delle password di hashing in primo luogo ). Questo problema può essere risolto, ma finisce per essere molto più complicato dell'uso di valori casuali per resettare i token e archiviare i loro hash in una tabella.
Nota importante: non lo implementerei da solo senza ulteriori verifiche, mi sono appena inventato per la maggior parte del tempo. Dare agli altri la possibilità di sottolineare cosa ho sbagliato. Seriamente, l'ho già rovinato una volta.
Quali proprietà richiede un token di reimpostazione della password?
- Identifica un utente specifico ed è valido per nessun altro
- Non più valido dopo la modifica della password
- Scade dopo qualche tempo
- Segreto e molto difficile da indovinare
- Il dump del database non attribuisce a un utente malintenzionato alcun token esistente o consente loro di generare token a piacimento
Dato che vuoi avere i token di reimpostazione senza doverli memorizzare in un database, ti suggerirei questo psuedocode per la generazione:
token = {
"user": [username],
"expiry": [unix timestamp]
}
// Very important!!!
// Password hash should be bcrypt or argon2 with good cost
// Secret application "pepper" is used to salt the key with HKDF, so a database dump won't allow generating reset tokens
key = hkdfsha256(user_password_hash, pepper)
return base64encode(token + hmacsha256(token, key))
E per verificare:
data = base64decode(data)
token = data[:-32]
submitted_hmac = data[-32:] // last 32 bytes are hmacsha256
if (token['expiry'] > now()) {
return false
}
// [retrieve password hash here]
key = hkdfsha256(user_password_hash, pepper)
return hmacsha256(token, key) == submitted_hmac
Esame
- Il nome utente identifica l'utente per cui il token è
- Derivare la chiave HMAC dall'hash della password significa che una volta che la password è stata modificata il token non è più valido
- Il controllo di scadenza impedisce a un token inutilizzato di rimanere valido per troppo tempo
- L'hash della password e il pepe rendono l'output HMAC (e quindi il token completo) non percettibile
- L'utilizzo di un pepe per derivare la chiave HMAC impedisce a un utente malintenzionato con un dump del database di generare token per tutti
Pro
- Nessuna memoria di database
- La validità del token è intrinsecamente legata alla password corrente
- Tutti i token attualmente validi possono essere invalidati facilmente cambiando il pepe
Contro
- Dimensioni token un po 'più grandi (108 caratteri base64 con nome utente di 15 caratteri), ma dovrebbe comunque essere ok per un URL in un messaggio di posta elettronica.
- Il token non è completamente opaco, gli utenti possono vedere le informazioni su utente e scadenza. Questo non ha alcun effetto sulla sicurezza, ma se vuoi evitare fastidiose domande da parte di persone che pensano che sia un problema potresti crittografarlo, anche se questo significa aggiungere un IV (se lo fai devi cifrare prima > em> HMAC).
- Se un utente malintenzionato ottiene un dump del database e il pepe, può generare token a volontà.
- Non c'è modo di sapere quanti token validi esistono in un dato momento o per quali utenti.
Alcuni pensieri
- L'utilizzo dell'intero hash della password significa che il sale farà parte della chiave HMAC, quindi dovrebbe contenere già una grande quantità di entropia anche se la password è terribile e il pepe viene esposto. Per attaccare questo schema, sia il database pepper che dovrebbero essere esposti.
- Se veramente vuoi rendere il token più piccolo, puoi troncare l'output HMAC a 16 byte, null terminare il nome utente e fare
username + 5 byte unix timestamp + 16 byte HMAC
. Ciò evita il problema 2038 con il timestamp e con un nome utente di 15 caratteri è di 52 caratteri base64. Non raccomanderei comunque di fare il timestamp a 5 byte, i 3 extra byte non sembrano valere la pena.
- Poiché la quantità di spazio che stai salvando è strettamente inferiore alla tabella degli utenti che stai già archiviando, non credo che ne valga la pena. Forse se non sei stato in grado di modificare lo schema del database e non aveva già un modo per memorizzare un token di ripristino.