Primero que nada, lo sé, no reinventes la rueda ... Pero hay una buena razón para esto.
Así que he estado implementando un algoritmo genérico de validación / generación de código de autenticación de 2 factores para reutilizarlo en múltiples aplicaciones y también dejarlo sin estado (el algoritmo no necesita almacenar nada en la base de datos, y no lo hace No requiere ningún dato de usuario en absoluto.
La aplicación que usa el código es responsable de establecer una carga útil y validarla dentro del proceso genérico de 2 fa.
Así es como funcionaría ...
1: la aplicación solicita un nuevo código 2FA que pasa una carga útil:
1.1 - carga útil: datos arbitrarios, como un ID de usuario y un ID de aplicación.
1.2: se crea un código aleatorio de 7 dígitos (utilizando bibliotecas aleatorias criptográficas seguras)
1.3: se selecciona un tiempo de caducidad, es decir, 3 minutos desde la hora actual, expresado como tiempo de época en segundos.
1.4 - datos: (caducidad, carga útil, código)
1.5: los datos se cifran utilizando el CTR de AES con un IV de 128 bits generado aleatoriamente y una clave secreta
1.6: tanto los datos encriptados como los IV se digieren a través de un HMAC que usa otra clave secreta para firmar estos dos valores.
1.7 - token final: 'datos cifrados en base64: IV: firma'
1.8: el token se devuelve a la IU del usuario (como una aplicación móvil o un usuario web de inicio de sesión)
1.9: el código se envía a través de SMS o correo electrónico
2: la aplicación envía el token original y el código proporcionado por el usuario al backend
2.1: el token se decodifica y la firma se valida
2.2: el token se descifra, se comprueba la caducidad y el código de usuario se compara con el código del token
2.3: la carga útil se devuelve a la aplicación, que realiza una validación adicional, como recuperar un usuario desde la identificación de usuario en la carga útil, etc. ...
¿Está roto? Podría preguntarse por qué no solo almacenar el código de 7 dígitos en la base de datos, sino que la idea es hacer que este sea completamente sin estado y genérico.
También tenga en cuenta: el punto final para la generación / validación de tokens se debe limitar de forma que no se pueda forzar con fuerza bruta.