¿Es este algoritmo para una cadena aleatoria criptográficamente segura?

4

He juntado este algoritmo (si se puede llamar así) a partir de varios bits de código que he visto en línea, y me pregunto qué tan criptográficamente seguro es. Se utiliza para generar contraseñas:

function seed_random() {
    $seed = crc32(uniqid(sha1(microtime(true) . getmypid()), true));
    mt_srand($seed);
    $n = mt_rand(1, 200);
    for ($i = 0; $i < $n; $i++) {
        mt_rand();
    }
}

function mt_rand_custom($min, $max) {
    seed_random();
    return mt_rand($min, $max);
}

function random_password($length) {
    $legal_characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'_!@#$^*?<>[]{}~(),";
    seed_random();
    $command = 'head -c 500 /dev/urandom | tr -dc %s | head -c %s; echo';
    return shell_exec(sprintf($command, $legal_characters, $length));
}

function generate_password() {
    return random_password(10);
}

Sé que la seguridad, especialmente en un entorno web, depende de muchas otras cosas (por ejemplo, la contraseña que se envía a través de SSL) pero tengo curiosidad acerca de este conjunto específico de funciones. Gracias por los comentarios.

EDITAR: Aunque la muestra ya no está usando la función mt_random_custom, cualquier comentario sobre eso también sería muy apreciado, así que lo dejé en.

    
pregunta Ricardo Altamirano 12.08.2011 - 22:39
fuente

2 respuestas

8

En tu código, tienes para completar $legal_characters con la lista de caracteres que aceptas como parte de una contraseña. Por ejemplo:

$legal_characters = "abcdefghijklmnopqrstuvwxyz";

si quieres contraseñas que consistan en letras minúsculas (todas letras latinas en minúsculas, pero no otros caracteres ni acentos).

Este código es un poco raro; utiliza mt_rand() (un PRNG interno) sembrado con la hora actual y el ID del proceso para obtener la longitud de la contraseña, entre 15 y 60 caracteres. Luego usa /dev/urandom para la contraseña, que es inteligente ya que mt_rand() no es criptográficamente segura (especialmente porque la ID del proceso no es algo secreto, y tampoco lo es la hora actual).

La generación de la contraseña real funciona así: produce 500 bytes aleatorios (desde /dev/urandom ), luego elimina todos aquellos que no están en el conjunto de caracteres aceptados (que es el " tr "), y finalmente trunca de nuevo Secuencia de caracteres restante a la longitud deseada. Este proceso genera secuencias uniformemente aleatorias, por lo que es bueno, y /dev/urandom es el PRNG apropiado para eso. Tenga en cuenta, sin embargo, algunas advertencias:

  • Si el conjunto de "caracteres legales" es pequeño, podría terminar con una contraseña más corta. P.ej. Si desea contraseñas con solo dígitos del 1 al 6, habrá, en promedio, solo 12 bytes iguales en los 500 bytes aleatorios. El código no tiene ningún seguro para "demasiado corto". Si establece $legal_characters en todas las 26 letras minúsculas, entonces aproximadamente 1 byte en 10 será legal (los valores de bytes varían de 0 a 255, y 26/256 está cerca de 1/10) y, en promedio, el " tr "parte producirá unos 50 caracteres, y es extremadamente improbable que produzca menos de 20. Sin embargo, vale la pena señalar que debería ser una prueba de fallos.

  • No tiene mucho sentido tener una longitud variable para la contraseña. Si una contraseña de longitud mínima es aceptable en términos de seguridad, entonces todas las contraseñas podrían tener esa longitud. Y si es no aceptable, ¿por qué usas esa longitud mínima? Es mejor que uses una sola longitud fija, en lugar de un rango. Esto le permitiría eliminar seed_random() y mt_rand_custom() , simplificando sustancialmente el código.

  • Una "contraseña" es algo que un ser humano podrá escribir y memorizar, por lo tanto, "palabra". ¿Memorizará una secuencia de 60 caracteres? Estoy seguro de que existe un nombre médico para tal condición.

  • Debe establecer la longitud de las contraseñas en un valor "apropiado" de tal manera que la entropía sea lo suficientemente alta. Si hay x caracteres legales y la longitud de la contraseña es n , entonces la entropía será xn . Lo que se necesita de la entropía depende del uso previsto. Para autenticar usuarios en un sitio web, a través de un túnel SSL apropiado, 2 40 ("40 bits" de entropía) es más que suficiente, lo que se traduce en 9 letras minúsculas (26 9 es mayor que 2 40 ).

  • El código usa unixismos ( /dev/urandom , head , tr ...) y tendrá dificultades para ejecutarse, por ejemplo, en un servidor Windows (y PHP también ejecuta Windows ).

Resumen: las contraseñas serán seguras, pero el código es extraño. Debe eliminar la longitud de la variable, y si sus usuarios pueden tragar contraseñas de 60 caracteres, felicítelos por mí. O venderlos a un zoológico. Aquí hay un código simplificado que debería estar bien (advertencia: no uso PHP, así que estoy improvisando aquí):

function generate_password() {
    return shell_exec('head -c 500 /dev/urandom | tr -dc abcdefghijklmnopqrstuvwxyz | head -c 9; echo');
}
    
respondido por el Thomas Pornin 12.08.2011 - 23:47
fuente
3

Análisis. Su código tiene algunas deficiencias:

  1. Uso defectuoso del sistema (). Sospecho que su comando de shell no va a hacer lo que quiere, porque no ha citado varios metacaracteres de shell. En lugar de tr -dc %s , necesita tr -dc '%s' , y luego necesita escapar de la comilla simple en $ legal_characters.

  2. Conjunto de caracteres demasiado amplio. Esto no generará contraseñas que puedan usarse de manera confiable en todos los sitios. Muchos sitios imponen restricciones sobre qué caracteres están permitidos en las contraseñas (por ejemplo, pueden prohibir las comillas simples). Como resultado, si usa su script en la práctica, encontrará que a menudo genera contraseñas que algunos sitios rechazarán. Sugeriría limitar el rango de caracteres a a-zA-Z0-9, para una máxima portabilidad. Esto no reducirá significativamente la seguridad de la entropía o la contraseña, pero hará que su script sea más útil.

  3. Código muerto. Tienes un código muerto inútil ( mt_rand_custom() ). Ese código también es algo desagradable y no es seguro para propósitos criptográficos, pero eso es una tangente: si desea preguntar sobre ese código, hágalo en otra pregunta. No rellene dos preguntas en una sola publicación.

Solución El comando de shell de una línea de @Thomas Pornin es mucho mejor. Personalmente, lo mejoraría un poco al permitir mayúsculas y números:

head -c 500 /dev/urandom | tr -dc a-zA-Z0-9 | head -c 12; echo

Esto es suficiente para obtener 71 bits de entropía, lo que debería ser más que suficiente para la contraseña de un sitio web, y las contraseñas resultantes deben ser aceptadas por casi todos los sitios web disponibles.

(Si desea una contraseña más corta, reemplazar el 12 por 10 le otorga una contraseña de 10 caracteres con 59 bits de entropía, que probablemente sea suficiente para todos los fines razonables. Una contraseña de 8 caracteres tendría 47 bits de entropía, que probablemente también sea suficiente, siempre que la base de datos de hash de la contraseña del sitio web no se vea comprometida, y para la mayoría de los usuarios, eso no es lo principal de lo que deben preocuparse. Una contraseña de 8 caracteres específica del sitio es lo suficientemente bueno como para evitar ataques de adivinación de contraseñas en línea, que es probablemente la amenaza más importante a la que se enfrenta la mayoría de los usuarios).

    
respondido por el D.W. 13.08.2011 - 03:19
fuente

Lea otras preguntas en las etiquetas