我将发布一个答案,因为现有的一些答案都有以下问题之一:
- 字符空间比您想要的更小,因此暴力破解更容易或者密码必须更长才能具有相同的熵
- RNG不被认为是加密安全的
- 需要一些第三方库,我认为展示如何自己实现可能很有趣
这个答案将规避count/strlen
问题,因为生成的密码的安全性至少在我的看法中超越了你如何得到它。我还假设PHP > 5.3.0。
让我们将问题分解成组成部分:
- 使用一些安全的随机源获取随机数据
- 使用该数据并将其表示为某些可打印的字符串
首先,PHP > 5.3.0 提供了函数 openssl_random_pseudo_bytes
。请注意,虽然大多数系统使用的是加密强度较高的算法,但您仍需要进行检查,因此我们将使用一个包装器:
function strong_random_bytes($length)
{
$strong = false;
$bytes = openssl_random_pseudo_bytes($length, $strong);
if ( ! $strong)
{
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个字符。
如果我们想满足OP问题中的确切规范,那么需要做更多工作。在这里,我将放弃基础转换方法,采用快速而粗略的方法。由于62个字符的字母表,两种方法都需要生成更多的随机数。
对于结果中的额外字符,我们可以简单地将它们从结果字符串中丢弃。如果我们在字节串中使用8个字节,那么最多约25%的base64字符将是这些“不受欢迎”的字符,因此只需丢弃这些字符即可得到与OP所需长度一样长或更长的字符串。然后,我们只需截断它以达到确切的长度:
$dirty_pass = base64_encode(strong_random_bytes(8)));
$pass = substr(str_replace(['/', '+', '='], ['', '', ''], $dirty_pass, 0, 8);
如果你生成更长的密码,填充字符
=
在中间结果中所占比例会越来越小,因此,如果担心耗尽用于PRNG的熵池,则可以实现更精简的方法。