Я собираюсь опубликовать ответ, потому что некоторые из существующих ответов близки, но имеют один из:
- меньше места для символов, чем вы хотели, так что или грубое принуждение проще, или пароль должен быть длиннее для той же энтропии
- a RNG , который не считается криптографически безопасным
- требование для какой-либо сторонней библиотеки, и я подумал, что было бы интересно показать, что нужно сделать, чтобы сделать это самостоятельно
Этот ответ обойдет проблему count/strlen
, поскольку безопасность сгенерированного пароля, по крайней мере, ИМХО, выходит за пределы того, как вы туда попадаете. Я также собираюсь предположить, что PHP> 5.3.0.
Давайте разберем проблему на составные части:
- использовать какой-нибудь безопасный источник случайности для получения случайных данных
- используйте эти данные и представьте их в виде печатной строки
Для первой части PHP> 5.3.0 предоставляет функцию openssl_random_pseudo_bytes
. Обратите внимание, что хотя в большинстве систем используется криптографически стойкий алгоритм, вы должны проверить, чтобы мы использовали оболочку:
/**
* @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;
}
Для второй части мы будем использовать base64_encode
, поскольку она принимает строку байтов и создает последовательность символов, алфавит которых очень близок к алфавиту, указанному в исходном вопросе. Если мы не возражаем против появления символов +
, /
и =
в конечной строке, и мы хотим, чтобы результат был длиной не менее $n
символов, мы могли бы просто использовать:
base64_encode(strong_random_bytes(intval(ceil($n * 3 / 4))));
Коэффициент 3/4
обусловлен тем фактом, что в результате кодирования base64 получается строка, длина которой как минимум на треть больше, чем строка байта. Результат будет точным для $n
, кратным 4 и до 3 символов длиннее в противном случае. Так как дополнительные символы являются преимущественно символом заполнения =
, если у нас по какой-то причине было ограничение, что пароль должен быть точной длины, то мы можем урезать его до желаемой длины. Это особенно связано с тем, что для данного $n
все пароли должны заканчиваться одинаковым количеством паролей, поэтому у злоумышленника, имеющего доступ к итоговому паролю, будет угадать до 2 символов меньше.
Для дополнительного кредита, если бы мы хотели выполнить точную спецификацию, как в вопросе ОП, нам пришлось бы сделать немного больше работы. Я собираюсь отказаться от базового подхода к конвертации и перейти к быстрому и грязному. Оба должны генерировать больше случайности, чем будет использовано в результате в любом случае из-за длинного алфавита из 62 записей.
Для дополнительных символов в результате мы можем просто отбросить их из полученной строки. Если мы начнем с 8 байтов в нашей байтовой строке, то примерно 25% символов base64 будут этими «нежелательными» символами, так что простое отбрасывание этих символов приводит к тому, что строка не короче, чем хотел OP. Тогда мы можем просто обрезать его, чтобы получить точную длину:
$dirty_pass = base64_encode(strong_random_bytes(8)));
$pass = substr(str_replace(['/', '+', '='], ['', '', ''], $dirty_pass, 0, 8);
Если вы генерируете более длинные пароли, символ заполнения =
образует все меньшую и меньшую пропорцию промежуточного результата, так что вы можете реализовать более гибкий подход, если использование ресурсов энтропии, используемых для PRNG, является проблемой.