Я собираюсь опубликовать ответ, потому что некоторые из существующих ответов близки, но имеют один из:
- Меньшее пространство символов, чем вы хотели, так что или грубое принуждение проще, или пароль должен быть длиннее для той же энтропии
- 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.