generate - autenticacion por token php
la mejor práctica para generar token aleatorio para contraseña olvidada (6)
Quiero generar identificador para contraseña olvidada. He leído que puedo hacerlo usando la marca de tiempo con mt_rand (), pero algunas personas dicen que la marca de tiempo puede 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 hay muchas preguntas por aquí, pero cada vez me siento más confundido después de leer opiniones diferentes de las diferentes personas.
Esto puede ser útil siempre que necesite un token muy muy aleatorio
<?php
echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
?>
La versión anterior de la respuesta aceptada ( md5(uniqid(mt_rand(), true))
) es insegura y solo ofrece alrededor de 2 ^ 60 resultados posibles, dentro del rango de una búsqueda de fuerza bruta en aproximadamente una semana por un bajo -Auditor de presupuesto:
-
mt_rand()
es predecible (y solo suma hasta 31 bits de entropía) -
uniqid()
solo agrega hasta 29 bits de entropía -
md5()
no agrega entropía, simplemente la mezcla de manera determinista
Como una clave DES de 56 bits puede ser forzada por fuerza bruta en aproximadamente 24 horas , y una caja 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, es posible que prácticamente se pierda información de temporización e inferir los primeros N bytes de un token de restablecimiento válido .
Dado que la pregunta es sobre "mejores prácticas" y se abre con ...
Quiero generar identificador para contraseña olvidada
... 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 entero mayor que 15).
En PHP 5, puedes usar random_compat
para exponer la misma API.
Alternativamente, bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM))
si tiene ext/mcrypt
instalado. Otro buen one-liner es bin2hex(openssl_random_pseudo_bytes($n))
.
Separación de la búsqueda del Validador
Extracto de mi trabajo previo sobre cookies seguras de "recordarme" en PHP , la única manera efectiva de mitigar la pérdida de tiempo antes mencionada (generalmente 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) ...
CREATE TABLE account_recovery (
id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT
userid INTEGER(11) UNSIGNED NOT NULL,
token CHAR(64),
expires DATETIME,
PRIMARY KEY(id)
);
... debe agregar una columna más, selector
, como ese:
CREATE TABLE account_recovery (
id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT
userid INTEGER(11) UNSIGNED NOT NULL,
selector CHAR(16),
token CHAR(64),
expires DATETIME,
PRIMARY KEY(id),
KEY(selector)
);
Usar 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 almacenado en la base de datos usando hash_equals()
.
Código de ejemplo
Generar un token de reinicio en PHP 7 (o 5.6 con random_compat) con PDO:
$selector = bin2hex(random_bytes(8));
$token = random_bytes(32);
$urlToEmail = ''http://example.com/reset.php?''.http_build_query([
''selector'' => $selector,
''validator'' => bin2hex($token)
]);
$expires = new DateTime(''NOW'');
$expires->add(new DateInterval(''PT01H'')); // 1 hour
$stmt = $pdo->prepare("INSERT INTO account_recovery (userid, selector, token, expires) VALUES (:userid, :selector, :token, :expires);");
$stmt->execute([
''userid'' => $userId, // define this elsewhere!
''selector'' => $selector,
''token'' => hash(''sha256'', $token),
''expires'' => $expires->format(''Y-m-d/TH:i:s'')
]);
Verificación del token de restablecimiento proporcionado por el usuario:
$stmt = $pdo->prepare("SELECT * FROM account_recovery WHERE selector = ? AND expires >= NOW()");
$stmt->execute([$selector]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (!empty($results)) {
$calc = hash(''sha256'', hex2bin($validator));
if (hash_equals($calc, $results[0][''token''])) {
// The reset token is valid. Authenticate the user.
}
// Remove the token from the DB regardless of success or failure.
}
Estos fragmentos de código no son soluciones completas (evité la validación de entrada y las integraciones de framework), pero deberían servir como un ejemplo de qué hacer.
Puede usar echo str_shuffle (''ASGDHFfdgfdre5475433fd'');
También puede usar DEV_RANDOM, donde 128 = 1/2 la longitud del token generado. El siguiente código genera 256 tokens.
$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));
esto responde mejor al azar
$token = bin2hex(openssl_random_pseudo_bytes(16));
En PHP, use random_bytes()
. Motivo: está buscando la forma de obtener un token de recordatorio de contraseña y, si es una credencial de inicio de sesión por única vez, entonces realmente tiene que proteger los datos (que es - cuenta de usuario completa)
Entonces, el código será el siguiente:
//$length = 78 etc
$token = bin2hex(random_bytes($length));
Actualización : las versiones anteriores de esta respuesta se referían a uniqid()
y eso es incorrecto si hay una cuestión de seguridad y no solo unicidad. uniqid()
es esencialmente solo microtime()
con alguna codificación. Hay formas simples de obtener predicciones precisas de la microtime()
en su servidor. Un atacante puede emitir una solicitud de restablecimiento de contraseña y luego probar un par de posibles tokens. Esto también es posible si se utiliza more_entropy, ya que la entropía adicional es similarmente débil. Gracias a @NikiC y por señalar esto.
Para más detalles ver