martes, 1 de octubre de 2013

Probabilidades y palabrotas

Situación típica: escribimos un fragmento de código para generar contraseñas más o menos aleatorias:

$src = 'abcdefghijkmlnopqrstuvwxyz0123456789';
$new = "";
$x = 0;
while($x < 8){
  $new .= substr($src, rand(0, strlen($src)-1), 1);
  $x++;
}

Estas líneas generan una cadena aleatoria de 8 caracteres alfanumérico. ¿Cuál es la probabilidad de que salga alguna palabrota? Tengo que recordar mis rudimentos de combinatoria, pero podemos asegurar que es una probabilidad bastante baja.

Pues bien, por baja que sea, no es cero. Caso verídico: he tenido una queja de una persona que se quejaba de que en la contraseña que le enviamos ponía "puta".

Vamos a comprobarlo mediante fuerza bruta:

<?php

function generarCadena($src, $len) {
    $x = 0;
    $new = "";
    while($x < $len) {
      $new .= substr($src, rand(0, strlen($src)-1), 1);
      $x++;
    }
    return $new;
}

ini_set('display_errors', FALSE);
$opts = getopt('s:l:w:');
if(!$opts['w']) die("-w");
$word = $opts['w'];
$src = isset($opts['s']) ? $opts['s'] : 'abcdefghijkmlnopqrstuvwxyz0123456789';
$len = $opts['l'] && is_numeric($opts['l']) ? (int)$opts['l'] : 8;
$count = 0;

while(true) {
    $new = generarCadena($src, $len);
    if(strpos($new, $word) !== FALSE) {
        die("Encontramos '$word' en la cadena '$new' tras ".number_format($count, 0, ",", ".")." iteraciones.n");
    }
    $count++;
}

?>

En este fragmento de código pedimos una palabra y empezamos a generar cadenas con una longitud de $len a partir de la semilla $src hasta que la encontramos.  Veamos cuánto tarda en aparecer la palabra "puta":

david@localhost:~/dev$ while(true); do php testRandomString.php -w puta  -l 8; done
Encontramos 'puta' en la cadena 'putarzd7' tras 113.588 iteraciones.
Encontramos 'puta' en la cadena 'tputag94' tras 745.111 iteraciones.
Encontramos 'puta' en la cadena 'oputa6in' tras 540.359 iteraciones.
Encontramos 'puta' en la cadena 'vputazze' tras 321.592 iteraciones.
Encontramos 'puta' en la cadena 'putarx1t' tras 331.196 iteraciones.
Encontramos 'puta' en la cadena 'puta75cs' tras 782.809 iteraciones.
Encontramos 'puta' en la cadena 'crputazr' tras 180.502 iteraciones.
Encontramos 'puta' en la cadena '13qqputa' tras 56.523 iteraciones.
Encontramos 'puta' en la cadena 'g1puta3n' tras 12.595 iteraciones.
Encontramos 'puta' en la cadena '6fg6puta' tras 190.036 iteraciones.
Encontramos 'puta' en la cadena 'oputayon' tras 644.111 iteraciones.
Encontramos 'puta' en la cadena 'ckputako' tras 170.121 iteraciones.
Encontramos 'puta' en la cadena 'jyputaob' tras 285.211 iteraciones.
Encontramos 'puta' en la cadena 'avputao5' tras 864.466 iteraciones.
Encontramos 'puta' en la cadena '88putaqa' tras 605.127 iteraciones.
Encontramos 'puta' en la cadena '9putawoi' tras 101.390 iteraciones.
Encontramos 'puta' en la cadena 'oputaik1' tras 15.688 iteraciones.
Encontramos 'puta' en la cadena '7puta940' tras 19.865 iteraciones.
Encontramos 'puta' en la cadena 'fjputajw' tras 355.393 iteraciones.
Encontramos 'puta' en la cadena 'putafpkn' tras 273.997 iteraciones.
Encontramos 'puta' en la cadena '7asputa5' tras 4.631 iteraciones.
Encontramos 'puta' en la cadena 'eputartt' tras 484.259 iteraciones.
Encontramos 'puta' en la cadena 'gz5putax' tras 266.215 iteraciones.
Encontramos 'puta' en la cadena '4ftpputa' tras 834.145 iteraciones.
Encontramos 'puta' en la cadena 'ryoputas' tras 318.049 iteraciones.
^C

No es tan improbable como pensábamos. En la línea marcada se ve cómo ha habido un ciclo de ejecuciones en las que sólo han sido necesarias 4.631 iteraciones para que aparezca la palabra que buscamos.

Conclusión: ojo con los generadores de palabras "aleatorios". Pueden generar patrones reconocibles que pueden ofender a algún usuario.

Por curiosidad, también lo he probado con palabras más largas:

david@localhost:~/dev$ while(true); do php testRandomString.php -w zorro -l 8; done
Encontramos 'zorro' en la cadena 'kozorro7' tras 14.560.786 iteraciones.
Encontramos 'zorro' en la cadena 'zorro9v7' tras 1.676.779 iteraciones.
Encontramos 'zorro' en la cadena 'zorro6i3' tras 6.871.325 iteraciones.
^C

david@localhost:~/dev$ while(true); do php testRandomString.php -w cabron -l 8; done
Encontramos 'cabron' en la cadena '0hcabron' tras 935.995.307 interaciones.
^C