Quiero generar un identificador para la contraseña olvidada. Leí que puedo hacerlo usando la marca de tiempo con mt_rand (), pero algunas personas dicen que la marca de tiempo podría no ser única cada vez. Así que estoy un poco confundido aquí. ¿Puedo hacerlo usando la marca de tiempo con esto?
Pregunta
¿Cuál es la mejor práctica para generar tokens aleatorios / únicos de longitud personalizada?
Sé que se hacen muchas preguntas por aquí, pero me siento más confundido después de leer diferentes opiniones de diferentes personas.
Respuestas:
En PHP, use
random_bytes()
. Razón: está buscando la manera de obtener un token de recordatorio de contraseña y, si se trata de una credencial de inicio de sesión única, entonces realmente tiene datos para proteger (que es - cuenta de usuario completa)Entonces, el código será el siguiente:
Actualización : se refería a versiones anteriores de esta respuesta
uniqid()
y eso es incorrecto si hay una cuestión de seguridad y no solo de unicidad.uniqid()
es esencialmente solomicrotime()
con algo de codificación. Hay formas sencillas de obtener predicciones precisas de lamicrotime()
en su servidor. Un atacante puede emitir una solicitud de restablecimiento de contraseña y luego probar con un par de tokens probables. Esto también es posible si se usa more_entropy, ya que la entropía adicional es igualmente débil. Gracias a @NikiC y @ScottArciszewski por señalar esto.Para obtener más detalles, consulte
fuente
random_bytes()
solo está disponible a partir de PHP7. Para versiones anteriores, la respuesta de @yesitsme parece ser la mejor opción.$length
? ¿La identificación del usuario? ¿O que?Esto responde a la solicitud 'mejor aleatoria':
La respuesta 1 de Adi de Security.StackExchange tiene una solución para esto:
1. Adi, lunes 12 de noviembre de 2018, Celeritas, "Generación de un token imposible de adivinar para correos electrónicos de confirmación", 20 de septiembre de 2013 a las 7:06, https://security.stackexchange.com/a/40314/
fuente
openssl_random_pseudo_bytes($length)
- soporte: PHP 5> = 5.3.0, ....................................... ................... (Para PHP 7 y posteriores, userandom_bytes($length)
) ...................... .................... (Para PHP por debajo de 5.3 - no use PHP por debajo de 5.3)La versión anterior de la respuesta aceptada (
md5(uniqid(mt_rand(), true))
) es insegura y solo ofrece aproximadamente 2 ^ 60 resultados posibles, dentro del rango de una búsqueda de fuerza bruta en aproximadamente una semana para un atacante de bajo presupuesto:mt_rand()
es predecible (y solo suma 31 bits de entropía)uniqid()
solo suma hasta 29 bits de entropíamd5()
no agrega entropía, simplemente la mezcla de manera deterministaDado que una clave DES de 56 bits puede ser forzada en aproximadamente 24 horas , y un caso promedio tendría aproximadamente 59 bits de entropía, podemos calcular 2 ^ 59/2 ^ 56 = aproximadamente 8 días. Dependiendo de cómo se implemente esta verificación de token, podría ser posible prácticamente filtrar información de tiempo e inferir los primeros N bytes de un token de reinicio válido .
Dado que la pregunta es sobre "mejores prácticas" y comienza con ...
... podemos inferir que este token tiene requisitos de seguridad implícitos. Y cuando agrega requisitos de seguridad a un generador de números aleatorios, la mejor práctica es usar siempre un generador de números pseudoaleatorios criptográficamente seguro (abreviado CSPRNG).
Usando un CSPRNG
En PHP 7, puede usar
bin2hex(random_bytes($n))
(donde$n
es un número entero mayor que 15).En PHP 5, puede utilizar
random_compat
para exponer la misma API.Alternativamente,
bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM))
si haext/mcrypt
instalado. Otra buena frase esbin2hex(openssl_random_pseudo_bytes($n))
.Separar la búsqueda del validador
Extrayendo de mi trabajo anterior sobre cookies seguras "recordarme" en PHP , la única forma efectiva de mitigar la filtración de tiempo antes mencionada (normalmente introducida por la consulta de la base de datos) es separar la búsqueda de la validación.
Si su tabla se ve así (MySQL) ...
... necesitas agregar una columna más
selector
, así:Use un CSPRNG Cuando se emite un token de restablecimiento de contraseña, envíe ambos valores al usuario, almacene el selector y un hash SHA-256 del token aleatorio en la base de datos. Use el selector para tomar el hash y la ID de usuario, calcule el hash SHA-256 del token que el usuario proporciona con el que está almacenado en la base de datos
hash_equals()
.Código de ejemplo
Generando un token de reinicio en PHP 7 (o 5.6 con random_compat) con PDO:
Verificación del token de restablecimiento proporcionado por el usuario:
Estos fragmentos de código no son soluciones completas (evité la validación de entrada y las integraciones del marco), pero deberían servir como ejemplo de qué hacer.
fuente
hash('sha256', bin2hex($token))
, 2) verificar conif (hash_equals(hash('sha256', $validator), $results[0]['token'])) {...
? ¡Gracias!id
como selector? Quiero decir, la clave principal de laaccount_recovery
mesa. No necesitamos una capa adicional de seguridad para el selector, ¿verdad? ¡Gracias!id:secret
está bien.selector:secret
está bien.secret
en sí mismo no lo es. El objetivo es separar la consulta de la base de datos (que tiene pérdidas de tiempo) del protocolo de autenticación (que debe ser de tiempo constante).openssl_random_pseudo_bytes
enrandom_bytes
su lugar si está ejecutando PHP 5.6? Además, ¿no debería agregar solo el selector y no el validador en la cadena de consulta del enlace?También puede usar DEV_RANDOM, donde 128 = 1/2 de la longitud del token generado. El código siguiente genera 256 tokens.
fuente
MCRYPT_DEV_URANDOM
másMCRYPT_DEV_RANDOM
.Esto puede ser útil siempre que necesite un token muy, muy aleatorio
fuente