如何生成一个随机、唯一、包含字母和数字的字符串?

493

如何使用数字和字母生成一个随机且唯一的字符串用于验证链接?就像在网站上创建账户并通过电子邮件发送带有链接的邮件,您需要点击该链接以验证您的账户一样。

我如何使用PHP生成此类字符串?


1
你所需要的仅是字符串和均匀分布的随机数。 - Artelius
12
嗨,安德鲁,你应该选择“Scott”作为正确答案。 Scott正在使用OpenSSL的加密安全伪随机数生成器(CSPRNG),它将基于您的平台选择最安全的熵源。 - rook
3
阅读此内容的任何人,如果在2015年之后,请务必注意:https://paragonie.com/blog/2015/07/how-safely-generate-random-strings-and-integers-in-php 大多数最佳答案或多或少存在缺陷... - rugk
OpenSSL和uniqid是不安全的。使用类似于Random::alphanumericString($length)或者Random::alphanumericHumanString($length)这样的东西。 - caw
31个回答

1
function codeGenerate() {
  $randCode  = (string)mt_rand(1000,9999);
  $randChar  = rand(65,90);
  $randInx   = rand(0,3);
  $randCode[$randInx] = chr($randChar);
  return $randCode;
}
echo codeGenerate();

输出

38I7
33V7
E836
736U

1

可以使用这段代码。我测试了35,000,00个ID,没有重复。

<?php
$characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_';
$result = '';
for ($i = 0; $i < 11; $i++)
    $result .= $characters[mt_rand(0, 63)];?>

您可以根据需要自由修改它。如果您有任何建议,请随时评论。建议在使用这些ID之前检查数据库中的每个ID,以确保您的数据库具有100%的唯一ID。


这绝对需要一个像microtime()这样的时间组件。 - ALZlper

1

在阅读之前的例子后,我想到了这个:

protected static $nonce_length = 32;

public static function getNonce()
{
    $chars = array();
    for ($i = 0; $i < 10; $i++)
        $chars = array_merge($chars, range(0, 9), range('A', 'Z'));
    shuffle($chars);
    $start = mt_rand(0, count($chars) - self::$nonce_length);
    return substr(join('', $chars), $start, self::$nonce_length);
}

我复制了数组[0-9,A-Z]十次并打乱元素顺序,然后随机选择一个起始点来使用substr()函数,以使结果更加“有创意” :) 你可以将[a-z]和其他元素添加到数组中,增加或减少复制次数,比我更有创造力。


1
我经常使用这个函数来生成自定义的随机字母数字字符串... 希望这能帮到你。
<?php
  function random_alphanumeric($length) {
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12345689';
    $my_string = '';
    for ($i = 0; $i < $length; $i++) {
      $pos = random_int(0, strlen($chars) -1);
      $my_string .= substr($chars, $pos, 1);
    }
    return $my_string;
  }
  echo random_alphanumeric(50); // 50 characters
?>

它生成的示例为:Y1FypdjVbFCFK6Gh9FDJpe6dciwJEfV6MQGpJqAfuijaYSZ86g

如果您想与另一个字符串进行比较以确保其是唯一序列,可以使用此技巧...

$string_1 = random_alphanumeric(50);
$string_2 = random_alphanumeric(50);
while ($string_1 == $string_2) {
  $string_1 = random_alphanumeric(50);
  $string_2 = random_alphanumeric(50);
  if ($string_1 != $string_2) {
     break;
  }
}
echo $string_1;
echo "<br>\n";
echo $string_2;

它生成了两个独特的字符串:

qsBDs4JOoVRfFxyLAOGECYIsWvpcpMzAO9pypwxsqPKeAmYLOi

Ti3kE1WfGgTNxQVXtbNNbhhvvapnaUfGMVJecHkUjHbuCb85pF

希望这正是您所需要的...


1

一种简单的解决方案是通过丢弃非字母数字字符将base 64转换为字母数字。

这个方法使用random_bytes()函数以获取具有密码学安全性的结果。

function random_alphanumeric(int $length): string
{
    $result='';
    do
    {
        //Base 64 produces 4 characters for each 3 bytes, so most times this will give enough bytes in a single pass
        $bytes=random_bytes(($length+3-strlen($result))*2);
        //Discard non-alhpanumeric characters
        $result.=str_replace(['/','+','='],['','',''],base64_encode($bytes));
        //Keep adding characters until the string is long enough
        //Add a few extra because the last 2 or 3 characters of a base 64 string tend to be less diverse
    }while(strlen($result)<$length+3);
    return substr($result,0,$length);
}

编辑:我重新访问了这个问题,因为我需要更灵活的解决方案。下面是一个比上面表现更好的解决方案,并提供了指定ASCII字符集任何子集的选项:

<?php
class RandomText
{
    protected
        $allowedChars,
        //Maximum index to use
        $allowedCount,
        //Index values will be taken from a pool of this size
        //It is a power of 2 to keep the distribution of values even
        $distributionSize,
        //This many characters will be generated for each output character
        $ratio;
    /**
     * @param string $allowedChars characters to choose from
     */
    public function __construct(string $allowedChars)
    {
        $this->allowedCount = strlen($allowedChars);
        if($this->allowedCount < 1 || $this->allowedCount > 256) throw new \Exception('At least 1 and no more than 256 allowed character(s) must be specified.');
        $this->allowedChars = $allowedChars;
        //Find the power of 2 equal or greater than the number of allowed characters
        $this->distributionSize = pow(2,ceil(log($this->allowedCount, 2)));
        //Generating random bytes is the expensive part of this algorithm
        //In most cases some will be wasted so it is helpful to produce some extras, but not too many
        //On average, this is how many characters needed to produce 1 character in the allowed set
        //50% of the time, more characters will be needed. My tests have shown this to perform well.
        $this->ratio = $this->distributionSize / $this->allowedCount;
    }

    /**
     * @param int $length string length of required result
     * @return string random text
     */
    public function get(int $length) : string
    {
        if($length < 1) throw new \Exception('$length must be >= 1.');
        $result = '';
        //Keep track of result length to prevent having to compute strlen()
        $l = 0;
        $indices = null;
        $i = null;
        do
        {
            //Bytes will be used to index the character set. Convert to integers.
            $indices = unpack('C*', random_bytes(ceil(($length - $l) * $this->ratio)));
            foreach($indices as $i)
            {
                //Reduce to the smallest range that gives an even distribution
                $i %= $this->distributionSize;
                //If the index is within the range of characters, add one char to the string
                if($i < $this->allowedCount)
                {
                    $l++;
                    $result .= $this->allowedChars[$i];
                }
                if($l >= $length) break;
            }
        }while($l < $length);
        return $result;
    }
}

0

Scott,是的,你说得很对,提供了一个好的解决方案!谢谢。

我还需要为每个用户生成唯一的API令牌。以下是我的方法,我使用了用户信息(用户ID和用户名):

public function generateUniqueToken($userid, $username){

        $rand = mt_rand(100,999);
    $md5 = md5($userid.'!(&^ 532567_465 ///'.$username);

    $md53 = substr($md5,0,3);
    $md5_remaining = substr($md5,3);

    $md5 = $md53. $rand. $userid. $md5_remaining;

    return $md5;
}

请看一下,如果有任何改进的意见,请告诉我。谢谢。

0

我认为这是使用的最佳方法。

str_shuffle(md5(rand(0,100000)))

0

这是一个有趣的例子

public function randomStr($length = 16) {
    $string = '';
        
    while (($len = strlen($string)) < $length) {
        $size = $length - $len;
            
        $bytes = random_bytes($size);
            
        $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
    }
        
        return $string;
}

从 Laravel 偷来的 这里


0

这是我在一个项目中使用的代码,它运行良好并生成一个唯一随机令牌

$timestampz=time();

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


$tokenparta = generateRandomString();


$token = $timestampz*3 . $tokenparta;

echo $token;

请注意,我将时间戳乘以三倍,以便让任何用户对于这个令牌是如何生成的感到困惑 ;)
希望能对你有所帮助 :)

1
请注意,我将时间戳乘以三来制造混淆,以防止任何用户想知道这个令牌是如何生成的。这听起来像是通过混淆来实现安全性的做法。https://dev59.com/EXRB5IYBdhLWcg3wuZfo - Harry
这不是“混淆安全性”,因为时间戳随后被添加到生成的随机字符串中,这就是我们返回的实际最终字符串 ;) - Fery Kaszoni

-1

我认为所有现有想法的问题在于它们可能是独一无二的,但不是绝对独一无二的(正如Dariusz Walczak回复loletech所指出的)。我有一个独特的解决方案。它需要您的脚本具有某种记忆功能。对我而言,这就是SQL数据库。您也可以简单地将其写入某个文件中。这里有两个实现方法:

第一种方法:拥有两个字段而不是1个字段提供独一无二性。第一个字段是一个非随机但唯一的ID编号(第一个ID为1,第二个为2...)。如果您使用SQL,则只需使用AUTO_INCREMENT属性定义ID字段即可。第二个字段不是唯一的,但是是随机的。可以使用其他技术中已经提到的任何方法来生成它。Scott的想法很好,但是md5很方便,对于大多数目的来说可能已经足够:

$random_token = md5($_SERVER['HTTP_USER_AGENT'] . time());

第二种方法:基本上是相同的想法,但最初选择一个将被生成的字符串的最大数量。这可能只是一个非常大的数字,比如一万亿。然后做同样的事情,生成一个ID,但零填充它,使所有ID具有相同的数字位数。然后只需将ID与随机字符串连接起来即可。对于大多数目的来说,它已经足够随机了,但ID部分也将确保它是唯一的。


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