Este artículo analiza los problemas relacionados con la generación de números aleatorios utilizado con fines fines criptográficos. PHP 5 no proporciona un mecanismo fácil para la generación de números aleatorios criptográficamente fuertes, pero PHP 7 lo resolverá introduciendo un par de funciones CSPRNG.
¿Qué es CSPRNG?
Citando a la Wikipedia, un generador de números pseudo-aleatorios criptográficamente seguro (CSPRNG) es un Generador de números pseudo-aleatorios (PRNG) con características que lo hacen adecuado para su uso en criptografía.
Un CSPRNG podría ser útil principalmente para:
- Generación de claves (por ejemplo, generación de passwords complicadas)
- Crear contraseñas aleatorias para nuevas cuentas de usuario
- Sistemas de cifrado
Un aspecto central para mantener un alto nivel de seguridad es la alta calidad de la aleatoriedad.
CSPRNG en PHP 7
PHP 7 introduce dos nuevas funciones que se pueden utilizar para CSPRNG: random_bytes y random_int.
La función random_bytes devuelve una cadena y acepta como entrada un entero que representa la longitud en bytes que es lo que se devolverá.
$bytes = random_bytes('10'); var_dump(bin2hex($bytes)); //posible salida: string(20) "7dfab0af960d359388e6"
La función random_int devuelve un número entero dentro del rango dado.
var_dump(random_int(1, 100)); //posible salida: 27
Las fuentes de aleatoriedad de las funciones anteriores son diferentes en función del entorno:
- En Windows, siempre se utilizará CryptGenRandom().
- En otras plataformas, arc4random_buf () se utilizará si está disponible.
- A falta de los anteriores, se utilizará un sistema Linux getrandom (2) syscall.
- Si todo lo demás falla /dev/urandom se utilizará como reserva final.
- Si ninguna de las fuentes anteriores está disponible, entonces se arrojará un error.
Una prueba sencilla
Un buen sistema de generación de números aleatorios asegura la correcta "calidad" de lo generado. Para comprobar dicha calidad, se suele llevar a cabo una serie de pruebas estadísticas. Sin ahondar en temas estadísticos complejos, comparando un comportamiento conocido con el resultado de un generador de números puedes evaluar la calidad de lo devuelto por alguna de estas funciones.
Una prueba sencilla es el juego de los dados. Suponiendo que las probabilidades de sacar un seis con un dado son uno de cada seis, si tiro tres dados al mismo tiempo 100 veces, los valores esperados para 0, 1, 2, y 3 seises son aproximadamente:
0 seises = 57,9 veces
1 seises = 34,7 veces
2 seises = 6,9 veces
3 seises = 0,5 veces
Este es el código para reproducir la tirada de dados 1.000.000 veces:
$times = 1000000; $result = []; for ($i=0; $i<$times; $i++){ $dieRoll = array(6 => 0); $dieRoll[roll()] += 1; $dieRoll[roll()] += 1; $dieRoll[roll()] += 1; $result[$dieRoll[6]] += 1; } function roll(){ return random_int(1,6); } var_dump($result);
Probando el código anterior con random_int de PHP 7 y la función rand obtenemos:
Seises | Esperado | random_init | rand |
0 | 579000 | 579430 | 578179 |
1 | 347000 | 346927 | 347620 |
2 | 69000 | 68985 | 69586 |
3 | 5000 | 4658 | 4615 |
Para ver una mejor comparación entre rand y random_int podemos trazar los resultados en un gráfico en el cual se aplica una formula para aumentar las diferencias entre los valores:
php result - expected result / sqrt(expected)
El gráfico resultante es:
Aunque la combinación de los tres seises no funciona bien y la prueba es demasiado fácil para una aplicación real, podemos ver claramente que random_int se comporta mejor que el rand.
Además, la seguridad de una aplicación se incrementa con la ausencia de comportamientos predecibles y repetibles en el generador de números aleatorios adoptado.
¿Qué pasa con PHP 5?
Por defecto, PHP 5 no proporciona ningún generador de números pseudo-aleatorios fuerte. En realidad, hay algunas opciones como openssl_random_pseudo_bytes(), mcrypt_create_iv() o utilizar directamente usar el /dev/random o /dev/urandom con fread(). También exiten librerías como RandomLib o libsodium.
Si quieres empezar a utilizar un buen generador de números aleatorios y al mismo tiempo que sea compatible con PHP 7, puedes utilizar la librería random_compat. Esta librería permite el uso de random_bytes() y random_int() en tus proyectos con PHP 5.x.
La biblioteca puede ser instalada a través de Composer:
composer require paragonie/random_compat
require 'vendor/autoload.php'; $string = random_bytes(32); var_dump(bin2hex($string)); // string(64) "8757a27ce421b3b9363b7825104f8bc8cf27c4c3036573e5f0d4a91ad2aaec6f" $int = random_int(0,255); var_dump($int); // int(81)
La biblioteca random_compat utiliza un orden de preferencia distinto en comparación con el de PHP 7:
fread() /dev/urandom mcrypt_create_iv($bytes, MCRYPT_CREATE_IV) COM('CAPICOM.Utilities.1')->GetRandom() openssl_random_pseudo_bytes()
Un uso simple de la librería para generar un password podría ser:
$passwordChar = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; $passwordLength = 8; $max = strlen($passwordChar) - 1; $password = ''; for ($i = 0; $i < $passwordLength; ++$i) { $password .= $passwordChar[random_int(0, $max)]; } echo $password; //posible salida: 7rgM8GZk
Siempre se debe aplicar un generador de números pseudo-aleatorios criptográficamente seguro, y la librería random_compat proporciona una buena aplicación para ello.
Si deseas utilizar una fuente de datos al azar fiable, como has visto en el artículo, la sugerencia es comenzar tan pronto como sea posible con random_int y random_bytes.
Fuente: sitepoint.com