2009-08-21 11 views
15

Necesito un reemplazo para la función rand() de PHP que utiliza un generador de números aleatorios criptográficamente fuerte.reemplazar rand() con openssl_random_pseudo_bytes()

La función openssl_random_pseudo_bytes() le permite acceder al generador de números aleatorios fuertes, pero genera sus datos como una cadena de bytes. En cambio, necesito un número entero entre 0 y X.

Imagino que la clave es obtener la salida de openssl_random_pseudo_bytes() en un número entero, entonces puede hacer cualquier cálculo que necesite. Puedo pensar en algunas formas de "fuerza bruta" de convertir de una cadena de bytes a un entero, pero esperaba algo ... elegante.

+0

mt_rand() dará números aleatorios mucho mejor calidad que rand(), pero no es criptográficamente fuerte tampoco. – David

+0

cuidado, esta respuesta es incorrecta. para ser más específico, la parte "% $ range" es incorrecta. Tome la siguiente situación: desea generar números entre 1 (incluido) y 4 (excluyendo) (ya que realiza max-min). $ range = 3. Ahora 3 no se divide uniformemente en 2^8, lo que significa que 1 va a ser más común que 3. Tenga mucho cuidado, quería crear un generador de números aleatorios seguro, pero inadvertidamente lo hizo inseguro, esta es la razón por la cual se recomienda usar implementaciones existentes. – chacham15

+0

@ chacham15: Si entiendes correctamente, en un rango de 8 bits obtienes 85 juegos de [1,2,3] más un único [1,], lo que hace que sea más probable que aparezca un "1" alrededor de un 1,17% más alto que " 2 "o" 3 ", ¿verdad? Como resolverías este problema? Obviamente, usar una * implementación existente * no es una opción, o no habría hecho la pregunta. – tylerl

Respuesta

11

Usando sugerencias proporcionadas, he creado una gota -en reemplazo de rand() usando OpenSSL. Lo incluiré aquí para la posteridad.

La opción $ pedantic ofrece resultados sin sesgo al volver a empezar cuando los resultados no se distribuyan uniformemente en el rango posible.

function crypto_rand($min,$max,$pedantic=True) { 
    $diff = $max - $min; 
    if ($diff <= 0) return $min; // not so random... 
    $range = $diff + 1; // because $max is inclusive 
    $bits = ceil(log(($range),2)); 
    $bytes = ceil($bits/8.0); 
    $bits_max = 1 << $bits; 
    // e.g. if $range = 3000 (bin: 101110111000) 
    // +--------+--------+ 
    // |....1011|10111000| 
    // +--------+--------+ 
    // bits=12, bytes=2, bits_max=2^12=4096 
    $num = 0; 
    do { 
     $num = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes))) % $bits_max; 
     if ($num >= $range) { 
      if ($pedantic) continue; // start over instead of accepting bias 
      // else 
      $num = $num % $range; // to hell with security 
     } 
     break; 
    } while (True); // because goto attracts velociraptors 
    return $num + $min; 
} 
+0

He agregado una respuesta con una guía sobre cómo puede usar CryptoLib para hacer esto como un reemplazo. Esto es mucho más seguro (y tiene un código más eficiente) y también proporciona un comprobador de repetibilidad. – mjsa

+0

@mjsa Aunque estoy seguro de que su biblioteca de cifrado es excelente, esta pregunta fue sobre la mecánica del código en sí misma, en lugar de una solicitud para que alguien construya otra biblioteca para hacerlo. – tylerl

+0

Esta no es una mala solución. Estoy ayudando a respaldar el 'random_int()' de PHP 7 en proyectos de PHP 5, y nuestro enfoque funciona incluso cuando '$ max - $ min> PHP_INT_MAX'. Puede encontrar nuestros esfuerzos en Github en [random_compat] (https://github.com/paragonie/random_compat) si desea analizar su propia perspectiva. –

8

La página de manual para openssl_random_pseudo_bytes() tiene un ejemplo que creo que desea. Simplemente puede llamar al bin2hex() en la salida de openssl_random_pseudo_bytes() para convertir a un número hexadecimal, luego hexdec() en ese valor para convertir a decimal.

$rand_num = hexdec(bin2hex(openssl_random_pseudo_bytes($length, $strong))); 

En ese momento puede hacer cualquier cálculo que desee para obtener un valor en el rango que necesita. La otra opción (trampa) que podría tener es ejecutar un comando del sistema para generar un número aleatorio: hay algunas buenas opciones para generadores de números aleatorios para varios sistemas operativos disponibles en línea.

+0

Esto no responde completamente la pregunta, vea las otras respuestas a continuación. – Andrew

1

Bueno, simplemente use hexdec en el resultado de openssl_random_pseudo_bytes y obtendrá su número entero. Es tan elegante como se pone :)

print hexdec('45261b8f'); 

> 1160125327 
+0

No responde la pregunta de operaciones ya que rand toma un mínimo y máximo y esto solo devuelve un número aleatorio. – nate

+0

No directamente, pero es bastante trivial agregar esto, como se puede ver en la solución proporcionada por el operador. –

2

Aquí está una versión de las soluciones anteriores, que no utiliza la función recursiva se llama:

function secure_rand($min,$max) { 
    $range = $max - $min + 1; 
    if ($range == 0) return $min; 
    $length = (int) (log($range,2)/8) + 1; 
    $max = pow(2, 8 * $length); 
    $num = $max + 1; // Hackish, I know.. 
    while ($num > $max) { 
     $num = hexdec(bin2hex(openssl_random_pseudo_bytes($length,$s))); 
    } 
    return ($num % $range) + $min; 
} 
-1
function ($min,$max){ 
    $range = $max-$min+1; 
    do{ 
     $result = floor($range*(hexdec(bin2hex(openssl_random_pseudo_bytes(4)))/0xffffffff)); 
    } while($result == $range);  
    return $result + $min; 
} 
0

La forma más sencilla de hacer esto (y la más segura de todas las opciones aquí) es el uso de CryptoLib que tiene una función randomInt que proporciona una gota en el reemplazo de rand.

Primera descarga de CryptoLib y pegarlo en su proyecto: https://github.com/IcyApril/CryptoLib

dos, caen en este código. sustituya la ruta/a/con el directorio de la cryptolib.php y minutos como máximo con su número mínimo y máximo:

<?php 
    require_once('path/to/cryptoLib.php'); 

    $min = 1; 
    $max = 5; 

    $randomNum = CryptoLib::randomInt($min, $max); 
?> 

La documentación completa CryptoLib está en: https://cryptolib.ju.je/

2

A partir de PHP 7 ya está disponible, la La forma más fácil de resolver este problema es reemplazar todas las instancias de mt_rand con random_int.

(Suponiendo que haya actualizado, es decir.)