El problema con el que se encuentra es que si el hash de la contraseña es todo lo que se necesita para generar un token válido, un atacante con un volcado de base de datos puede generar tokens de reinicio para todos (lo que en primer lugar anula el propósito del hashing de las contraseñas). ). Este problema se puede resolver, pero termina siendo mucho más complicado que usar valores aleatorios para restablecer tokens y almacenar sus hashes en una tabla.
Nota importante: yo mismo no lo implementaría sin un examen más detenido, solo se me ocurrió esto por encima de mi cabeza por el gusto de hacerlo. Dale a los demás la oportunidad de señalar lo que me equivoqué. En serio, ya lo arruiné una vez.
¿Qué propiedades necesita un token de restablecimiento de contraseña?
- Identifica un usuario específico y no es válido para nadie más
- Ya no es válido después de cambiar la contraseña
- Vence después de un tiempo
- Secreto y muy difícil de adivinar
- El volcado de la base de datos no le otorga a un atacante ningún token existente ni le permite generar tokens a voluntad
Ya que quieres tener tokens restablecidos sin tener que almacenarlos en una base de datos, sugeriría este psuedocode para la generación:
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))
Y para verificar:
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
Examen
- El nombre de usuario identifica al usuario para el que está el token
- Derivar la clave HMAC a partir del hash de la contraseña significa que una vez que se cambie la contraseña, el token ya no será válido.
- La verificación de caducidad evita que un token no utilizado permanezca válido por mucho tiempo
- El hash y pepper de la contraseña hacen que la salida HMAC (y, por lo tanto, el token completo) no se pueda adiestrar.
- El uso de un pimiento para derivar la clave HMAC evita que un atacante con un volcado de base de datos genere tokens para todos
Pros
- No hay almacenamiento en la base de datos
- La validez del token está intrínsecamente vinculada a la contraseña actual
- Todas las fichas válidas actualmente se pueden invalidar fácilmente cambiando el pimiento
Contras
- Tamaño de token algo grande (108 caracteres base64 con un nombre de usuario de 15 caracteres), pero aún así debería estar bien para una URL en un correo electrónico.
- El token no es completamente opaco, los usuarios pueden ver la información de vencimiento y del usuario. Esto no afecta la seguridad, pero si desea evitar las preguntas molestas de las personas que piensan que es un problema, también podría cifrarlo, aunque esto significa agregar un IV (si lo hace, debe cifrar antes de HMAC).
- Si un atacante obtiene un volcado de base de datos y el pimiento, puede generar tokens a voluntad.
- No hay forma de saber cuántos tokens válidos existen en un momento dado, o para qué usuarios.
Algunos pensamientos
- Usar el hash de la contraseña completa significa que la sal formará parte de la clave HMAC, por lo que ya debería contener una gran cantidad de entropía, incluso si la contraseña es terrible y el pimiento se expone. Para atacar este esquema, tanto la base de datos de pimienta como tendrían que estar expuestas.
- Si realmente desea reducir el token, puede truncar la salida HMAC a 16 bytes, terminar el nombre de usuario en nulo y hacer
username + 5 byte unix timestamp + 16 byte HMAC
. Esto evita el problema 2038 con la marca de tiempo, y con un nombre de usuario de 15 caracteres se ha reducido a 52 caracteres base64. Sin embargo, no recomendaría hacer la marca de tiempo de 5 bytes, los 3 bytes adicionales no parecen valer la pena.
- Dado que la cantidad de espacio que está ahorrando es estrictamente más pequeña que la tabla de usuarios que ya está almacenando, no creo que valga la pena. Tal vez si no pudiste alterar el esquema de la base de datos y no tenía una forma de almacenar un token de reinicio.