PHP随机字符串生成器

1009

我正在尝试在PHP中创建一个随机字符串,但是使用以下代码没有任何输出:

<?php
    function RandomString()
    {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $randstring = '';
        for ($i = 0; $i < 10; $i++) {
            $randstring = $characters[rand(0, strlen($characters))];
        }
        return $randstring;
    }

    RandomString();
    echo $randstring;

我做错了什么?


306
生成短字符串的一行代码解决方案为 substr(md5(rand()), 0, 7); 祝好运... - tasmaniski
7
@tasmaniski 的解决方案没问题,但是随机性较低!在你的示例中,可以生成的随机字符串数量受整数大小的限制(最大为 2^32)。如果我想要更长的字符串,那么在 @tasmaniski 的解决方案中,不同字符串的数量最多达到 2^32,但在另一个解决方案中,它会增加到 (62^n)。请注意,这里的 n 表示字符串长度。 - Manu
13
你忘记将每个新生成的字符添加到字符串中,而是直接覆盖它。应该是 $randstring .= $characters.。 - Spock
3
@CaptainLightning,请您将已接受的答案更换为更安全的答案之一。 :) - Scott Arciszewski
3
strlen($characters) => strlen($characters) - 1 - 字符串长度从1开始 - Zippp
显示剩余9条评论
69个回答

1655

具体回答这个问题,有两个问题:

  1. $randstring 在你输出它时没有被定义。
  2. 在循环中,这些字符没有被正确地连接在一起。

下面是带有更正的代码片段:

function generateRandomString($length = 10) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[random_int(0, $charactersLength - 1)];
    }
    return $randomString;
}

使用以下调用输出随机字符串:

// Echo the random string.
// Optionally, you can give it a desired string length.
echo generateRandomString();

请注意,之前版本的这个答案使用了rand()而不是random_int(),因此生成的随机字符串是可预测的。因此,根据该答案的建议,已更改为更安全的方式。


36
与其在循环的比较条件中调用一个方法,不如浪费一个额外的变量。 - Rico Sonntag
249
为什么不直接使用类似于substr(str_shuffle(MD5(microtime())), 0, 10);这样的代码,而要做那么多的工作呢? - SpYk3HH
7
谢谢您提供的代码,但请将 rand() 更改为 mt_rand()。我使用了您的方法,但遇到了很多名字冲突的问题。 - Nate
7
你能否意识到这个方法的效率多低下?你在每次循环时都要检查一遍字符串的长度!应该在进入循环之前将循环内的strlen缓存在一个变量中。 - brettwhiteman
6
这不是一个好的解决方案。生成的字符串会很容易被猜到。 :( - Scott Arciszewski
显示剩余12条评论

434

注意:str_shuffle()在内部使用不适用于加密目的(例如生成随机密码)的rand()。您需要一个安全的随机数生成器。它还不允许字符重复。

另一种方法。

更新 (现在可以生成任何长度的字符串):

function generateRandomString($length = 10) {
    return substr(str_shuffle(str_repeat($x='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil($length/strlen($x)) )),1,$length);
}

echo  generateRandomString();  // OR: generateRandomString(24)
那就这样吧。 :)

67
此代码将输出字符串,其中每个字符不会出现超过一次(从而使其对暴力攻击更加脆弱),并且不会输出超过62个字符的任何内容。 - David
20
不要这样做,这非常脆弱。您的随机字符串越长,它就越脆弱。 - Abhi Beckert
28
可能它很脆弱,但却快速且易于使用。根据使用情况而定,这样做是可以的——我使用它是因为我不需要安全性或强度,只需要一个快速的随机数发生器。我正在编写单元测试,这给了我一个适当的随机输入来进行测试。 - SDC
25
@FranciscoPresencia,它不安全的原因是因为它从不重复使用相同的字符。这使它成为一个非常糟糕的密码生成器。请大家停止点赞它,它完全不安全,绝不能使用。随着密码长度的增加,在暴力破解中需要测试的字符数会变少,因为你不需要测试任何先前使用过的字符。 - Abhi Beckert
11
我支持您关于避免在随机密码中使用此选项的评论。然而,我不同意这个答案不应该得到点赞。我已经给这个选项点了赞,因为它是生成随机字符串的有效单行解决方案(这个问题的原始主题)。 - bwright
显示剩余15条评论

431

对于这个问题,有很多答案,但是没有一个利用了加密安全的伪随机数生成器 (CSPRNG)。

简单、安全、正确的答案是使用RandomLib,不要重复造轮子。

对于那些坚持要发明自己的解决方案的人,PHP 7.0.0将提供random_int()来实现此目的;如果你仍在使用PHP 5.x,我们编写了PHP 5的random_int()兼容库,所以即使在升级到PHP 7之前也可以使用新的API。

在PHP中安全生成随机整数并不是一项微不足道的任务。在将自制算法投入生产之前,您应该始终与您所在的StackExchange密码学专家进行核对。

有了安全的整数生成器,使用CSPRNG生成随机字符串就像散步一样简单。

创建一个安全、随机的字符串

/**
 * Generate a random string, using a cryptographically secure 
 * pseudorandom number generator (random_int)
 *
 * This function uses type hints now (PHP 7+ only), but it was originally
 * written for PHP 5 as well.
 * 
 * For PHP 7, random_int is a PHP core function
 * For PHP 5.x, depends on https://github.com/paragonie/random_compat
 * 
 * @param int $length      How many characters do we want?
 * @param string $keyspace A string of all possible characters
 *                         to select from
 * @return string
 */
function random_str(
    int $length = 64,
    string $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
): string {
    if ($length < 1) {
        throw new \RangeException("Length must be a positive integer");
    }
    $pieces = [];
    $max = mb_strlen($keyspace, '8bit') - 1;
    for ($i = 0; $i < $length; ++$i) {
        $pieces []= $keyspace[random_int(0, $max)];
    }
    return implode('', $pieces);
}

用法:

$a = random_str(32);
$b = random_str(8, 'abcdefghijklmnopqrstuvwxyz');
$c = random_str();

演示: https://3v4l.org/IMJGF(忽略 PHP 5 的失败; 它需要 random_compat)


3
为了增加安全性,在函数开头添加 $keyspace = str_shuffle($keyspace ); - Jevgenij Dmitrijev
24
你所说的“更安全”是什么意思?我们已经在使用一个安全的随机数生成器了。 - Scott Arciszewski
12
生成安全随机字符串需要使用加密学上的安全随机性。搜索“如何在PHP中生成随机字符串”的人会从一个安全的答案中受益,而不是一个不安全的答案。 - Scott Arciszewski
4
@ Scott Arciszewski 您不需要每次创建随机字符串时都创建加密随机字符串。问题是关于创建随机字符串,与安全无关。您假设该字符串将在安全敏感的环境中使用,并添加了一个复杂性层,这会使问题的核心变得模糊。 - JG Estiot
76
使用random_int()代替rand()mt_rand()对开发人员来说不会增加复杂性,同时可以提供更高的安全性。寻求快速解决方案的 StackOverflow 用户可能不知道他们正在构建的东西是否需要安全性。如果我们给出默认安全的答案,即使从他们的角度来看这是完全偶然的,他们也会创造一个更安全的互联网。为什么你会反对这个目标,这对我来说是个谜。 - Scott Arciszewski
显示剩余12条评论

217

这将创建一个长度为20个字符的十六进制字符串:

$string = bin2hex(openssl_random_pseudo_bytes(10)); // 20 chars

在 PHP 7 中 (random_bytes()):

$string = base64_encode(random_bytes(10)); // ~14 characters, includes /=+
// or
$string = substr(str_replace(['+', '/', '='], '', base64_encode(random_bytes(32))), 0, 32); // 32 characters, without /=+
// or
$string = bin2hex(random_bytes(10)); // 20 characters, only 0-9a-f

2
请注意,直到php 5.6之前,openssl_random_pseudo_bytes()没有使用加密强度算法。相关错误:https://bugs.php.net/bug.php?id=70014 - acidtv
请注意,此函数能生成的最小字符串长度为2。如果您向 openssl_random_pseudo_bytes() 函数传递一个字节长度小于等于1的参数,则不会返回任何内容。另外,请注意当使用 bin2hex(openssl_random_pseudo_bytes($length / 2)) 函数时,由于您正在处理整数,它将自动删除模数并使用下一个最低的2的倍数。因此,$length = 19 将产生长度为18的字符串。 - Nathan F.
建议将其用作文件内容,可以通过fgets($fp, 1024)打开,并且适用于所有有问题的长行文件编辑器:function string_rand($len, $split="\n") { return substr(chunk_split(bin2hex(openssl_random_pseudo_bytes(ceil($len / 2))), 1023, $split), 0, $len); } 如果应返回一行,则将$split设置为null。这样,您可以同时使用它来处理两种情况。 - mgutt
我非常喜欢 PHP 7 中的 random_bytes 解决方案,但如果您需要字符串具有特定数量的字符,类似这样的东西是否可行?$string = substr(base64_encode(random_bytes($size)), 0, $size); - Programster
哈!真是个好惊喜!:https://github.com/PHPMailer/PHPMailer/blob/a69cfb1860c36f607d0822ee88d8a67da35fa5d8/src/PHPMailer.php#L2753 - Artfaith
显示剩余3条评论

115

@tasmaniski:你的答案对我有用。我遇到了同样的问题,我建议那些寻找相同答案的人也可以尝试一下。以下是@tasmaniski的解决方案:

<?php 
    $random = substr(md5(mt_rand()), 0, 7);
    echo $random;
?>

这里有一个YouTube视频,向我们展示如何创建随机数


11
如果你的字符串只是基于一个随机整数,为什么不直接使用随机整数呢?对于这种情况,哈希并不能增加任何深度,而且通过截取哈希值,实际上还会减少你最初拥有的熵值。 - The Surrican
4
请记住,使用md5会导致结果在30个字符以内且始终为小写字母。 - Will B.
4
另外,rand() 不应用于加密。如果您想生成一个具有加密强度的字符串,请使用 openssl_random_pseudo_bytes - mattkgross
md5(rand()) 只提供了 2^32 种可能的值。这意味着在平均 2^16 个随机字符串后,您将期望有一个重复出现。 - Scott Arciszewski
3
嗯...我敢打赌OP不是在谈论“安全随机字符串”。这个解决方案紧凑、简单且易于记忆,适用于正确的使用情况。而且,如果你需要处理可能的冲突,为什么不在随机字符串前面/后面添加时间戳呢? :) - Erenor Paz
显示剩余2条评论

51

根据您的应用程序(我想生成密码),您可以使用

$string = base64_encode(openssl_random_pseudo_bytes(30));

作为base64格式,它们可能包含=-以及请求的字符。您可以生成更长的字符串,然后过滤和修剪它以去除这些字符。

openssl_random_pseudo_bytes似乎是在php中生成适当随机数的推荐方法。为什么rand不使用/dev/random我不知道。


2
rand不使用/dev/urandom,因为它只在类posix环境中可用,而且不具备可移植性。 - MacroMan
3
@MacroMan 但是 openssl_random_pseudo_bytes() 是可移植的。 - Ja͢ck
4
这并不是错误的,但我建议你在丢弃不需要的字符时要谨慎。例如,请参考PHP联盟OAuth2服务器上的pull request - Scott Arciszewski
这是最好的答案之一。可惜它没有更多的支持。我建议您编辑您的答案,以更好地处理不需要的字符。 - jcuenod
我非常确定你列出的两个字符对于大多数当前的base64算法来说是不正确的。此外,base64占据的空间比普通字符更多,可以根据需要进行清理。如果您有足够的空间,请使用十六进制。它始终是安全的。 - David Spector
显示剩余2条评论

36

PHP 7+ 使用random_bytes函数生成具有密码学安全性的随机字节。

$bytes = random_bytes(16);
echo bin2hex($bytes);

PHP 5.3+ 使用openssl_random_pseudo_bytes函数生成伪随机字节。

da821217e61e33ed4b2dd96f8439056c


Possible output

$bytes = openssl_random_pseudo_bytes(16);
echo bin2hex($bytes);

可能的输出

e2d1254506fbb6cd842cd640333214ad


最好的使用情况可能是

function getRandomBytes($length = 16)
{
    if (function_exists('random_bytes')) {
        $bytes = random_bytes($length / 2);
    } else {
        $bytes = openssl_random_pseudo_bytes($length / 2);
    }
    return bin2hex($bytes);
}
echo getRandomBytes();

可能的输出

ba8cc342bdf91143


1
这不会生成给定长度的字符串。 - Timberman
getRandomBytes()(所有的)只适用于偶数长度。除此之外,这是一个更好、不那么可怕的答案。 - Dewi Morgan
@DewiMorgan,我猜任何哈希算法都会生成偶数长度。 - Madan Sapkota
嗯,更确切地说,bin2hex() 函数将不可避免地返回偶数个十六进制字符。对于奇数长度的情况,将输出截断一个字符 据我所知 不会影响熵... 但我承认这会为几乎所有用例增加几行无意义的代码。 - Dewi Morgan
所以在开头,$odd=$length%2; $length+=$odd;,在结尾返回substr(bin2hex($length), $odd); 就可以解决问题...但代价是可读性相当低。你的代码更清晰易懂,更易于维护,所以如果我要使用它,我可能会添加一个关于“仅适用于偶数长度”的注释,除非这真的很重要。 - Dewi Morgan
显示剩余2条评论

29

这里有一个简单的一行代码,可以生成真正的随机字符串,而不需要使用脚本级循环或OpenSSL库。

echo substr(str_shuffle(str_repeat('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', mt_rand(1,10))), 1, 10);

为了将其分解成清晰的参数

// Character List to Pick from
$chrList = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

// Minimum/Maximum times to repeat character List to seed from
$chrRepeatMin = 1; // Minimum times to repeat the seed string
$chrRepeatMax = 10; // Maximum times to repeat the seed string

// Length of Random String returned
$chrRandomLength = 10;

// The ONE LINE random command with the above variables.
echo substr(str_shuffle(str_repeat($chrList, mt_rand($chrRepeatMin,$chrRepeatMax))), 1, $chrRandomLength);

该方法的工作原理是随机重复字符列表,然后混洗组合字符串,并返回指定数量的字符。

您可以进一步随机化此过程,通过将$chrRandomLength替换为mt_rand(8, 15)(用于生成8到15个字符的随机字符串)来随机化返回字符串的长度。


很遗憾,但当一个字符被选择后,它再次出现的概率并不像仍留在池中的其他字符那样高。这里没有真正的随机性... - The Surrican
所有字符只会出现一次,因此很容易受到暴力猜测的攻击。 - venimus
1
这个算法很聪明,但它基于mt_rand生成确定性数字序列。输出可以通过获取或猜测盐来解码。真正的随机序列在任何情况下都不能被解码为熵较小的内容,比如一个小整数。一些系统通过测量人们打字或移动鼠标所需的时间等方式生成真实世界的熵,从而实现对真正随机性的良好近似。请注意,使用系统时间也不是真正的随机,因为攻击者有时可以拦截设备定时信号。 - David Spector
1
str_shuffle是确定性的,而不是真正的随机。 - David Spector
1
下降投票原因:1)声称mt_rand不是真正随机时,而不是“伪随机字符串”;声称字符选择具有相等的概率,这表明对统计学的误解。一旦从字符串中选择了第一个字符,则其在第二个字符的选择中的概率为9 /(10 * n),而所有其他字符的概率为10 /(10 * n)。这些东西很重要:在安全环境中,这样的错误是危险的。 - Dewi Morgan
显示剩余5条评论

26

实现这个函数的更好方法是:

function RandomString($length) {
    $keys = array_merge(range(0,9), range('a', 'z'));

    $key = "";
    for($i=0; $i < $length; $i++) {
        $key .= $keys[mt_rand(0, count($keys) - 1)];
    }
    return $key;
}

echo RandomString(20);

mt_rand函数在PHP 7中更加随机,参考这个这个。而rand函数是mt_rand的别名。


1
注意:mt_rand 不会生成加密安全的值。为此,您应该使用 PHP7 的 random_intrandom_bytes - jchook

25
function generateRandomString($length = 15)
{
    return substr(sha1(rand()), 0, $length);
}

Tada!


3
SHA1字符串不是随机的,某些字符出现的频率比其他字符高。如果您真正需要随机性,比如用于密码,使用它是不明智的。 - Kit Sunde
2
@KitSunde - 是的,sha不是随机的,rand()是随机的。而且sha对于这里所需的内容完全可以胜任。OP不需要加密级别的随机性。 - Davor
@Davor rant() 在 sha 不均匀分布时是随机的并不重要。如果你从一个不均匀的分布中随机选择,你会得到完全相同的不均匀分布。我强调的不是 "加密级别" 的随机性,如果是这样的话,你也不应该使用 rand()。从接受的答案中选择一个均匀分布需要很少的努力,并且与 rand() 一样随机,这是合理的。 - Kit Sunde
@KitSunde - 最终没有任何区别。这正是过度工程和过度设计的定义。 - Davor
@Davor 如果你不想过度设计,那么你可以这样做 function generateRandomString(){return "asdf"; /* Decided by fair dice roll */ }。;) 过度设计的定义并不是让一个函数变成7行长,以便它能够完成它所承诺的功能,并且在被要求超过40个字符时不会出现意外失败。;) 还有一种叫做“最小惊讶原则”的东西。但是,好吧,我们已经谈了,我想我很高兴你很高兴,我没有必要进一步讨论这个问题。 - Kit Sunde
显示剩余3条评论

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