Voy a publicar una respuesta porque algunas de las respuestas existentes están cerca pero tienen una de:
- un espacio de caracteres más pequeño que el deseado para que el forzamiento bruto sea más fácil o la contraseña debe ser más larga para la misma entropía
- un RNG que no se considera criptográficamente seguro
- un requisito para una biblioteca de terceros y pensé que podría ser interesante mostrar lo que se necesita para hacerlo usted mismo
Esta respuesta eludirá el count/strlen
problema ya que la seguridad de la contraseña generada, al menos en mi humilde opinión, trasciende cómo está llegando allí. También voy a asumir PHP> 5.3.0.
Analicemos el problema en las partes constituyentes que son:
- use alguna fuente segura de aleatoriedad para obtener datos aleatorios
- usar esos datos y representarlos como una cadena imprimible
Para la primera parte, PHP> 5.3.0 proporciona la función openssl_random_pseudo_bytes
. Tenga en cuenta que, si bien la mayoría de los sistemas usan un algoritmo criptográficamente fuerte, debe verificarlo, así que usaremos un contenedor:
/**
* @param int $length
*/
function strong_random_bytes($length)
{
$strong = false; // Flag for whether a strong algorithm was used
$bytes = openssl_random_pseudo_bytes($length, $strong);
if ( ! $strong)
{
// System did not use a cryptographically strong algorithm
throw new Exception('Strong algorithm not available for PRNG.');
}
return $bytes;
}
Para la segunda parte, usaremos base64_encode
ya que toma una cadena de bytes y producirá una serie de caracteres que tienen un alfabeto muy cercano al especificado en la pregunta original. Si no nos importa tener +
, /
y los =
caracteres aparecen en la cadena final y queremos un resultado de al menos $n
caracteres largos, simplemente podríamos usar:
base64_encode(strong_random_bytes(intval(ceil($n * 3 / 4))));
El 3/4
factor se debe al hecho de que la codificación base64 da como resultado una cadena que tiene una longitud al menos un tercio más grande que la cadena de bytes. El resultado será exacto por $n
ser un múltiplo de 4 y hasta 3 caracteres más de lo contrario. Dado que los caracteres adicionales son predominantemente el carácter de relleno =
, si por alguna razón tuvimos la restricción de que la contraseña tenga una longitud exacta, entonces podemos truncarla a la longitud que queramos. Esto se debe especialmente a que, para un determinado $n
, todas las contraseñas terminarían con el mismo número de ellas, de modo que un atacante que tuviera acceso a una contraseña resultante, tendría hasta 2 caracteres menos para adivinar.
Para obtener crédito adicional, si quisiéramos cumplir con las especificaciones exactas como en la pregunta del OP, entonces tendríamos que hacer un poco más de trabajo. Voy a renunciar al enfoque de conversión de base aquí e ir con uno rápido y sucio. Ambos necesitan generar más aleatoriedad de la que se utilizará en el resultado de todos modos debido al alfabeto largo de 62 entradas.
Para los caracteres adicionales en el resultado, simplemente podemos descartarlos de la cadena resultante. Si comenzamos con 8 bytes en nuestra cadena de bytes, entonces hasta aproximadamente el 25% de los caracteres base64 serían estos caracteres "indeseables", de modo que simplemente descartar estos caracteres da como resultado una cadena no más corta que la OP deseada. Entonces podemos simplemente truncarlo para llegar a la longitud exacta:
$dirty_pass = base64_encode(strong_random_bytes(8)));
$pass = substr(str_replace(['/', '+', '='], ['', '', ''], $dirty_pass, 0, 8);
Si genera contraseñas más largas, el carácter de relleno =
forma una proporción cada vez menor del resultado intermedio para que pueda implementar un enfoque más ágil, si es preocupante drenar el grupo de entropía utilizado para el PRNG.