Vou postar uma resposta porque algumas das respostas existentes são próximas, mas têm uma das seguintes:
- um espaço de caracteres menor do que o desejado, para que a força bruta seja mais fácil ou a senha precise ser maior para a mesma entropia
- um RNG que não é considerado criptograficamente seguro
- um requisito para uma biblioteca de terceiros e achei interessante mostrar o que é necessário para fazer você mesmo
Esta resposta contornará o count/strlen
problema, pois a segurança da senha gerada, pelo menos IMHO, transcende como você está chegando lá. Também vou assumir o PHP> 5.3.0.
Vamos dividir o problema nas partes constituintes que são:
- use alguma fonte segura de aleatoriedade para obter dados aleatórios
- use esses dados e os represente como uma sequência imprimível
Para a primeira parte, o PHP> 5.3.0 fornece a função openssl_random_pseudo_bytes
. Observe que, embora a maioria dos sistemas use um algoritmo criptograficamente forte, você deve verificar se usaremos um wrapper:
/**
* @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 a segunda parte, usaremos base64_encode
uma vez que é necessária uma sequência de bytes e produzirá uma série de caracteres que possuem um alfabeto muito próximo ao especificado na pergunta original. Se não nos importássemos em ter +
, /
e os =
caracteres aparecerem na sequência final e quisermos um resultado com pelo menos $n
caracteres, poderíamos simplesmente usar:
base64_encode(strong_random_bytes(intval(ceil($n * 3 / 4))));
O 3/4
fator se deve ao fato de a codificação base64 resultar em uma sequência que possui um comprimento pelo menos um terço maior que a sequência de bytes. $n
Caso contrário, o resultado será exato por ter múltiplos de 4 e até 3 caracteres. Como os caracteres extras são predominantemente o caractere de preenchimento =
, se, por algum motivo, tivermos uma restrição de que a senha tenha um tamanho exato, poderemos truncá-la para o tamanho desejado. Isso ocorre principalmente porque, para um determinado dado $n
, todas as senhas terminariam com o mesmo número delas, de modo que um invasor que tivesse acesso a uma senha de resultado teria até 2 caracteres a menos para adivinhar.
Para crédito extra, se quiséssemos atender às especificações exatas, como na pergunta do OP, teríamos que trabalhar um pouco mais. Vou renunciar à abordagem de conversão de base aqui e seguir uma rápida e suja. Ambos precisam gerar mais aleatoriedade do que será usado no resultado de qualquer maneira, devido ao alfabeto de 62 entradas.
Para os caracteres extras no resultado, podemos simplesmente descartá-los da sequência resultante. Se começarmos com 8 bytes em nossa cadeia de bytes, até cerca de 25% dos caracteres base64 seriam esses caracteres "indesejáveis", de modo que simplesmente descartar esses caracteres resultará em uma cadeia não menor que o OP desejado. Em seguida, podemos simplesmente truncá-lo para obter o tamanho exato:
$dirty_pass = base64_encode(strong_random_bytes(8)));
$pass = substr(str_replace(['/', '+', '='], ['', '', ''], $dirty_pass, 0, 8);
Se você gerar senhas mais longas, o caractere de preenchimento =
formará uma proporção cada vez menor do resultado intermediário, para que você possa implementar uma abordagem mais enxuta, se for necessário drenar o pool de entropia usado para o PRNG.