生成7个字符长的唯一随机字母数字组合

7

不需要有意义的单词 - 更像是随机密码生成,但关键是 - 它们应该是唯一的。我将用它来生成某种包/产品代码。哪种方法是最好的? :)


4
为什么你的密码必须是独一无二的? - Michael Haren
3
他/她说的是“像密码”,而不是“密码”。据我所知,这是一个产品代码,这些代码绝对必须是唯一的。 - Zarel
5
小提醒:如果你要将这些代码提供给用户,请注意,最终可能会有一组代码读作“F*CKYOU”。请翻译以上内容。 - nickf
2
@nickf:好观点。我有一个函数,可以交替生成辅音和元音字母,我用它来生成一些CAPTCHA 5个字符的字符串……在测试期间,我得到了一个写着merda(翻译= sh*t)的字符串,还有一个年长的客户因为得到了一个写着foder(翻译= to f*ck)的字符串而有些生气…… - Alix Axel
1
@Alix,过去我需要密码生成器时所做的就像Jon的答案一样,使用了$allowed_chars字符串,但是删除了所有元音字母以及可能被误解为其他字符的字符:1,l,0 - nickf
显示剩余4条评论
15个回答

6

通常不可能生成既具有唯一性又具有随机性的序列:显然,为了保持唯一性,算法必须考虑先前生成的序列元素,因此下一个元素实际上并不是真正的随机。

因此,您最好检测碰撞并重试(在您的特定情况下可能非常昂贵)。

如果您只限于7个字符,除了以下方法外,没有太多可以做:

$allowed_chars = 'abcdefghijklmnopqrstuvwxz';
$allowed_count = strlen($allowed_chars);
$password = null;
$password_length = 7;

while($password === null || already_exists($password)) {
    $password = '';
    for($i = 0; $i < $password_length; ++$i) {
        $password .= $allowed_chars{mt_rand(0, $allowed_count - 1)};
    }
}

这最终会为您提供一个新的密码。

然而,在我遇到类似情况时,我通常会选择更大的密码长度,这也恰好是流行哈希函数(例如md5)的十六进制表示大小。这样您就可以更轻松地操作,减少错误:

$password = time(); // even better if you have some other "random" input to use here

do {
    $password = md5(time().$password);
}
while (already_exists($password));

这还有一个额外的好处,即序列空间更大,因此碰撞会更少。您可以根据未来生成的密码数量选择哈希函数的大小,以“保证”低碰撞概率,从而减少对可能昂贵的already_exists函数的调用。


2
“alphanumeric” 意味着您需要在 $allowed_chars 变量中包含 0...9 :) - Catchwa

1

这是一种不需要哈希或循环的方法:

$password = sprintf(
    "%04s%03s",
    base_convert(mt_rand(0, pow(36, 4) - 1), 10, 36),
    base_convert(mt_rand(0, pow(36, 3) - 1), 10, 36)
);

正如其他一些人所提到的,确保唯一性更加复杂,而且应该是不必要的。你可以采用最简单的方法,在每个生成的密码末尾添加额外的字符,并逐个递增。


1

这里有一些看起来随机的东西,应该是独一无二的,并且在未来的时间里有7个字符:

echo base_convert(intval(microtime(true) * 10000), 10, 36);

或者为了更多的随机性和较少的独特性(每秒之间在100010000之间):

echo base_convert(mt_rand(1, 9) . intval(microtime(true) * 1000), 10, 36);

或者(每秒钟在10010000之间的唯一性)- 这可能是最好的选择:

echo base_convert(mt_rand(10, 99) . intval(microtime(true) * 100), 10, 36);

或者(每秒钟在1010000之间的唯一性):

echo base_convert(mt_rand(100, 999) . intval(microtime(true) * 10), 10, 36);

你懂的。


1
这里要注意整数溢出问题。intval(microtime(true) * 10000) == -65867797 - 这将给你一个6个字符的输出。 - nickf

1

一个随机的字母数字(基数36 = 0..9 + a..z)值,它有7个字符必须具有10进制表示在217678233678364164095之间,以下代码片段证明了这一点:

var_dump(base_convert('1000000', 36, 10));                   //  2176782336
var_dump(base_convert('zzzzzzz', 36, 10));                   // 78364164095

为了使其独一无二,我们必须依赖于一个不重复的因素,显而易见的选择是time()
var_dump(time());                                            //  1273508728
var_dump(microtime(true));                                   //  1273508728.2883

如果我们只想确保每秒至少有1个唯一代码的最小唯一性因素,我们可以执行以下操作:
var_dump(base_convert(time() * 2, 10, 36));                  // 164ff8w
var_dump(base_convert(time() * 2 + 1, 10, 36));              // 164ff8x
var_dump(base_convert(time() * 2 + 2, 10, 36));              // 164ff8y
var_dump(base_convert(time() * 2 + 3, 10, 36));              // 164ff8z

你会注意到这些代码不是随机的,你也会注意到time()1273508728)小于2176782336(7个字符代码的最小10进制表示),这就是为什么我要做time() * 2

现在让我们进行一些日期数学运算,以增加随机性和增加唯一性因素,同时遵守旧版本PHP(<5.0?)的整数限制:

var_dump(1 * 60 * 60);                                       //       3600
var_dump(1 * 60 * 60 * 24);                                  //      86400
var_dump(1 * 60 * 60 * 24 * 366);                            //   31622400
var_dump(1 * 60 * 60 * 24 * 366 * 10);                       //  316224000
var_dump(1 * 60 * 60 * 24 * 366 * 20);                       //  632448000
var_dump(1 * 60 * 60 * 24 * 366 * 30);                       //  948672000
var_dump(1 * 60 * 60 * 24 * 366 * 31);                       //  980294400
var_dump(PHP_INT_MAX);                                       // 2147483647

关于 PHP_INT_MAX 我不确定最近的 PHP 版本发生了什么变化,因为以下在 PHP 5.3.1 中明显可行,也许有人能够解释一下:
var_dump(base_convert(PHP_INT_MAX, 10, 36));                 // zik0zj
var_dump(base_convert(PHP_INT_MAX + 1, 10, 36));             // zik0zk
var_dump(base_convert(PHP_INT_MAX + 2, 10, 36));             // zik0zl
var_dump(base_convert(PHP_INT_MAX * 2, 10, 36));             // 1z141z2
var_dump(base_convert(PHP_INT_MAX * 2 + 1, 10, 36));         // 1z141z3
var_dump(base_convert(PHP_INT_MAX * 2 + 2, 10, 36));         // 1z141z4

我在这里有点迷失了,而且我很无聊,所以我会很快完成。我们可以使用几乎整个36进制字符集,并安全地生成连续代码,每秒最少保证1个唯一代码3.16887646年,使用以下方法:

base_convert(mt_rand(22, 782) . substr(time(), 2), 10, 36);

我刚刚意识到,由于mt_rand()的第一个参数,上述代码有时会返回重复的值,为了产生唯一的结果,我们需要稍微限制一下我们的基本36个字符集:

base_convert(mt_rand(122, 782) . substr(time(), 2), 10, 36);

请记住,上述值仍然是顺序的,为了使它们看起来随机,我们可以使用microtime(),但我们只能确保每秒10个代码在3.8个月内的唯一性因素:

base_convert(mt_rand(122, 782) . substr(number_format(microtime(true), 1, '', ''), 3), 10, 36);

这比我最初预期的要困难得多,因为有很多限制:

  • 使用整个36进制字符集
  • 生成看起来随机的代码
  • 在每秒钟的唯一性因素和唯一性的持久性之间进行权衡
  • PHP整数限制

如果我们可以忽略上述任何一个限制,那么这将变得更加容易,我相信这可以进一步优化,但就像我说的那样:这让我感到无聊。也许有人想接手这个项目。=)我饿了!=S


为什么你要从 1000000 开始,而不是 0000000 - sawa
@sawa:你有读问题和我回答的第一段吗? - Alix Axel
我的问题是针对你的第一段。你用var_dump(base_convert('1000000', 36, 10));证明了它,而不是0000000。我想问为什么?这个问题要求一个字符串。为什么一个字符串不能以'0'开头? - sawa
@sawa:嗯,你可以这样做,但是那样你就必须用0来填充它。我想我没有提到过这一点,因为时间戳永远不会有那种低值。 - Alix Axel

1

这是我最喜欢的做法。

$pretrimmedrandom = md5(uniqid(mt_rand(),true));
$trimmed =  substr($pretrimmedrandom ,0,7);

uniqid 使用当前时间生成一个非常独特的随机字符串。结果看起来像“3f456yg”。


0
$random = substr(hash('md5',openssl_random_pseudo_bytes(32)),0,7);

0

Galen的答案只允许在密码中使用每个字符一次。字符串中没有太多信息。不过可以进行简单更改:

$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
$passwordlength = 7;
for ($x = 1; $x <= $passwordlength; $x++) {
  $charlist .= $chars;
}
$temp_pw = substr( str_shuffle( $charlist ), 0, $passwordlength );

0
substr(str_shuffle(md5(microtime())),rand(0,21),7);

0
考虑到你在这里提到了密码,我会假设你需要一种安全的方法(即:别人不能根据知道其他密码猜测出别人的密码)。你可以使用以下方法:
  1. 选择一个主密码,例如“MasterPassword”
  2. 为生成的每个密码添加一个随机或顺序 nonce,例如“MasterPassword1”,“MasterPassword2”
  3. 对其执行密码哈希(SHA、MD5 等),并将哈希转换为十六进制表示,例如“ce7f181a44a4a5b7e43fe2b9a0b1f0c1”
  4. 截取所需的字符数,例如七个字符:“ce7f181”
  5. 检查是否之前已分配。如果没有,则将其返回作为您的密码。否则,请从第 2 步开始重复。

如果安全不是问题,则仅第 1 和第 2 步就足够了。如果安全是一个问题,那么除了你自己之外,没有人应该知道“MasterPassword”的值。


2
我对加密并不是很了解,所以无法用言语表达我对这个想法的不安...但它让我感到不舒服。你选择使用base16而不是例如base-26+26+10等其他方式,已经减少了可能性;此外(猜测),当你截断哈希时,会损害其完整性。不过,我承认,要求唯一密码也有点奇怪... - Michael Haren
在所有观点上都达成了一致。关于十六进制方面,我很乐意承认你是对的,字符数量越多越好。同样,在截断方面也有共识,但除非要求使用32个字符的密码(或更糟),否则无法避免。此外,哈希更多地用于生成与其他密码相似基础的高度独特的密码,而不是作为安全措施。实际上,也可以使用非加密哈希或CRC,但这会使主密码更容易受到攻击。 - Mac
3
密码哈希值不是唯一的。如果你截短它们,它们绝对不是唯一的。这种方法与仅生成随机字符串并检查它们是否被分配没有任何优势。 - Zarel
1
附言:我对加密货币有一定的了解,所以我可以用语言表达出我对这个想法的不安。 ;) - Zarel
好的,我之前回答用词不当。我从未意味着哈希将是唯一的,只是高度可能是唯一的。如果需要安全性,那么与非密码随机字符串生成器相比,这种方法有优势——对于攻击者来说,更难以反向工程化一个特定的其他用户的密码,给出生成的密码样本。你 可以 本质上将其视为使用密码PRNG(哈希)进行字符生成,在这种情况下,实际上这个答案与其他答案几乎没有区别。 - Mac
那条评论真的表达不清楚,现在我重新读一遍... 😉 - Mac

0

这是我解决这个问题的方法:

考虑到7个字符可以是26个字母(abc..z)或10个数字(01...9)之一,这使得有36种可能的字符。

每次您的应用程序生成新代码时,请将其全局变量递增。您可以使用“Hexatridecimal”转换器将此唯一编号转换为唯一字符串,并添加填充字符以组成其余的字符串。

看看这个链接。我认为这个人和你有同样的问题: http://www.codemaxima.com/2010/04/the-hexatridecimal-numbering-system/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接