如何使用数字和字母生成随机唯一的字符串以用于验证链接?就像您在网站上创建帐户一样,它会向您发送一封带有链接的电子邮件,您必须单击该链接才能验证您的帐户
如何使用PHP生成其中之一?

评论

您只需要字符串和均匀分布的随机数即可。

嗨,安德鲁,您应该选择斯科特作为正确答案。 Scott正在使用OpenSSL的加密安全伪随机数生成器(CSPRNG),它将根据您的平台选择最安全的熵源。

任何在2015年之后阅读此书的人,请在这里查看:paragonie.com/blog/2015/07/…大多数最重要的答案或多或少都存在缺陷...

OpenSSL和uniqid是不安全的。使用类似Random :: alphanumericString($ length)或Random :: alphanumericHumanString($ length)之类的东西。

#1 楼


安全公告:如果您的随机性会影响应用程序的安全性,则不应使用此解决方案。特别是,rand()uniqid()并不是加密安全的随机数生成器。请参阅Scott的答案以寻求安全的替代方法。


如果您不需要它随着时间的流逝而变得绝对独特,则:

md5(uniqid(rand(), true))

否则(假设您已经为用户确定了唯一的登录名):

md5(uniqid($your_user_login, true))


评论


两种方法都不能保证唯一性-md5函数的输入长度大于其输出的长度,并且根据en.wikipedia.org/wiki/Pigeonhole_principle,可以保证冲突。另一方面,依靠散列实现“唯一”标识的人群越多,发生至少一次碰撞的可能性就越大(请参阅en.wikipedia.org/wiki/Birthday_problem)。对于大多数解决方案而言,这种可能性可能很小,但仍然存在。

– Dariusz Walczak
2011年7月14日12:30



这不是生成随机值的安全方法。参见斯科特的答案。

–rook
2013年9月18日19:51

对于相对较快到期的电子邮件确认,我认为一个人一天有可能确认其他人的电子邮件的可能性很小,风险很小。但是,创建令牌后,您始终可以检查令牌是否已存在于数据库中,如果是这种情况,只需选择一个新令牌即可。但是,您花费在编写该代码段上的时间可能很浪费,因为它很可能永远不会运行。

–安德鲁(Andrew)
2014年5月26日在21:10

请注意,md5返回十六进制值,这意味着字符集限制为[0-9]和[a-f]。

– Thijs Riezebeek
15年5月15日在16:59

md5可能会发生冲突,您刚刚毁了uniqid的优势

–user151496
16-09-28在12:42

#2 楼

PHP 7标准库提供了random_bytes($length)函数,该函数生成加密安全的伪随机字节。
示例:
$bytes = random_bytes(20);
var_dump(bin2hex($bytes));

上面的示例将输出类似于以下内容:
string(40) "5fe69c95ed70a9869d9f9af7d8400a6673bb9ce9"

更多信息:http://php.net/manual/en/function.random-bytes.php
PHP 5(已过时)
我只是在研究如何解决同样的问题,但是我也希望我的功能创建一个令牌,该令牌也可以用于密码检索。这意味着我需要限制令牌被猜测的能力。因为uniqid是基于时间的,并且根据php.net“返回值与microtime()几乎没有区别”,所以uniqid不符合该标准。 PHP建议使用openssl_random_pseudo_bytes()来代替,以生成加密安全的令牌。
一个简短,简短的答案是:
bin2hex(openssl_random_pseudo_bytes($bytes))

,它将生成一个长度为$ bytes的字母数字字符的随机字符串* 2.不幸的是,它只有一个字母[a-f][0-9],但是它可以工作。

下面是我可以使它满足标准的最强大的功能(这是Erik的答案的实现版本)。
function crypto_rand_secure($min, $max)
{
    $range = $max - $min;
    if ($range < 1) return $min; // not so random...
    $log = ceil(log($range, 2));
    $bytes = (int) ($log / 8) + 1; // length in bytes
    $bits = (int) $log + 1; // length in bits
    $filter = (int) (1 << $bits) - 1; // set all lower bits to 1
    do {
        $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
        $rnd = $rnd & $filter; // discard irrelevant bits
    } while ($rnd > $range);
    return $min + $rnd;
}

function getToken($length)
{
    $token = "";
    $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
    $codeAlphabet.= "0123456789";
    $max = strlen($codeAlphabet); // edited

    for ($i=0; $i < $length; $i++) {
        $token .= $codeAlphabet[crypto_rand_secure(0, $max-1)];
    }

    return $token;
}

crypto_rand_secure($min, $max)可以替代rand()mt_rand。它使用openssl_random_pseudo_bytes帮助创建介于$ min和$ max之间的随机数。
getToken($length)创建在令牌中使用的字母,然后创建长度为$length的字符串。
资料来源:http:// us1。 php.net/manual/en/function.openssl-random-pseudo-bytes.php#104322

评论


同意纯文本密码存储非常糟糕! (我使用河豚鱼。)但是在生成令牌后,它允许令牌的持有者重设密码,因此,令牌在有效时与密码等效(并且被视为令牌)。

–斯科特
13年5月8日在1:44

嘿,我将您的出色代码与便捷的Kohana Text :: random()方法功能结合在一起,并创建了以下要点:gist.github.com/raveren/5555297

–raveren
13年5月10日在15:51

完善!我测试了10个字符作为长度。 100000个令牌,仍然没有重复!

–Sharpless512
2014-2-26在8:13

我使用了此过程,发现它非常慢。由于长度为36,因此在开发服务器上超时。长度为24时,仍需要约30秒。不知道我做错了什么,但是对我来说太慢了,我选择了另一个解决方案。

– Shane
2014年4月17日在1:25



您也可以使用base64_encode代替bin2hex来获得具有较大字母的较短字符串。

–尔江
2014年5月30日13:57

#3 楼

投票最多的解决方案的面向对象版本

我根据Scott的回答创建了一个面向对象的解决方案:

 <?php

namespace Utils;

/**
 * Class RandomStringGenerator
 * @package Utils
 *
 * Solution taken from here:
 * http://stackoverflow.com/a/13733588/1056679
 */
class RandomStringGenerator
{
    /** @var string */
    protected $alphabet;

    /** @var int */
    protected $alphabetLength;


    /**
     * @param string $alphabet
     */
    public function __construct($alphabet = '')
    {
        if ('' !== $alphabet) {
            $this->setAlphabet($alphabet);
        } else {
            $this->setAlphabet(
                  implode(range('a', 'z'))
                . implode(range('A', 'Z'))
                . implode(range(0, 9))
            );
        }
    }

    /**
     * @param string $alphabet
     */
    public function setAlphabet($alphabet)
    {
        $this->alphabet = $alphabet;
        $this->alphabetLength = strlen($alphabet);
    }

    /**
     * @param int $length
     * @return string
     */
    public function generate($length)
    {
        $token = '';

        for ($i = 0; $i < $length; $i++) {
            $randomKey = $this->getRandomInteger(0, $this->alphabetLength);
            $token .= $this->alphabet[$randomKey];
        }

        return $token;
    }

    /**
     * @param int $min
     * @param int $max
     * @return int
     */
    protected function getRandomInteger($min, $max)
    {
        $range = ($max - $min);

        if ($range < 0) {
            // Not so random...
            return $min;
        }

        $log = log($range, 2);

        // Length in bytes.
        $bytes = (int) ($log / 8) + 1;

        // Length in bits.
        $bits = (int) $log + 1;

        // Set all lower bits to 1.
        $filter = (int) (1 << $bits) - 1;

        do {
            $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));

            // Discard irrelevant bits.
            $rnd = $rnd & $filter;

        } while ($rnd >= $range);

        return ($min + $rnd);
    }
}
 



用法

 <?php

use Utils\RandomStringGenerator;

// Create new instance of generator class.
$generator = new RandomStringGenerator;

// Set token length.
$tokenLength = 32;

// Call method to generate random string.
$token = $generator->generate($tokenLength);
 


自定义字母

您可以根据需要使用自定义字母。
只需将带有受支持字符的字符串传递给构造函数或setter:

< pre class =“ lang-php prettyprint-override”> <?php $customAlphabet = '0123456789ABCDEF'; // Set initial alphabet. $generator = new RandomStringGenerator($customAlphabet); // Change alphabet whenever needed. $generator->setAlphabet($customAlphabet);

这是输出示例

 SRniGU2sRQb2K1ylXKnWwZr4HrtdRgrM
q1sRUjNq1K9rG905aneFzyD5IcqD4dlC
I0euIWffrURLKCCJZ5PQFcNUCto6cQfD
AKwPJMEM5ytgJyJyGqoD5FQwxv82YvMr
duoRF6gAawNOEQRICnOUNYmStWmOpEgS
sdHUkEn4565AJoTtkc8EqJ6cC4MLEHUx
eVywMdYXczuZmHaJ50nIVQjOidEVkVna
baJGt7cdLDbIxMctLsEBWgAw5BByP5V0
iqT0B2obq3oerbeXkDVLjZrrLheW4d8f
OUQYCny6tj2TYDlTuu1KsnUyaLkeObwa
 



我希望它将对某人有所帮助。干杯!

评论


对不起,我该怎么办?我做了$ obj = new RandomStringGenerator;但是我应该调用哪种方法?谢谢。

– sg552
2014年10月1日下午16:41

@ sg552,您应该像上面的示例一样调用generate方法。只需向其传递一个整数即可指定结果字符串的长度。

– Slava Fomin II
2014年12月2日在8:38

谢谢,它为我工作,但自定义字母没有。回显时输出为空。无论如何,我什至不知道第二个示例“自定义字母”的用途是什么,所以我认为我只保留第一个示例。谢谢。

– sg552
2014年12月3日21:24

不错,但是在大多数情况下都太过分了,除非您的项目严重依赖于随机生成的代码

– dspacejs
15年5月8日在11:47

是的,但不错,但是矫kill过正,尤其是考虑到它现在对于PHP 5.7几乎已被弃用

–安德鲁(Andrew)
16-3-21在17:30

#4 楼

我来晚了,但是我在这里有一些基于斯科特答案提供的功能的良好研究数据。因此,我为此5天的自动测试设置了一个Digital Ocean小滴,并将生成的唯一字符串存储在MySQL数据库中。
在此测试期间,我使用了5种不同的长度(5、10、15、20 ,50)和+/- 0.5百万条记录(每种长度都插入)。在我的测试过程中,只有5个长度生成了50万个+/- 3K副本,其余长度没有生成任何副本。因此,可以说,如果我们将长度等于或大于15的长度与Scott的函数一起使用,则可以生成高度可靠的唯一字符串。下表显示了我的研究数据:

更新:
我使用这些函数创建了一个简单的Heroku应用,该应用将令牌作为JSON响应返回。可以通过以下网址访问该应用程序:https://uniquestrings.herokuapp.com/api/token?length = 15
希望对您有所帮助。

评论


达亚姆,令人印象深刻!

–道格·罗斯(Doug Ross)
9月11日21:41

#5 楼

您可以使用UUID(通用唯一标识符),它可以用于从用户身份验证字符串到付款交易ID的任何目的。

UUID是一个16字节(128位)的数字。 UUID以其规范形式由32个十六进制数字表示,以连字符分隔的五个组显示,形式为8-4-4-4-12,表示总共36个字符(32个字母数字字符和四个连字符)。 br />
function generate_uuid() {
    return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
        mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
        mt_rand( 0, 0xffff ),
        mt_rand( 0, 0x0C2f ) | 0x4000,
        mt_rand( 0, 0x3fff ) | 0x8000,
        mt_rand( 0, 0x2Aff ), mt_rand( 0, 0xffD3 ), mt_rand( 0, 0xff4B )
    );

}


//通话功能

$transationID = generate_uuid();


一些示例输出将如下:

E302D66D-87E3-4450-8CB6-17531895BF14
22D288BC-7289-442B-BEEA-286777D559F2
51B4DE29-3B71-4FD2-9E6C-071703E1FF31
3777C8C6-9FF5-4C78-AAA2-08A47F555E81
54B91C72-2CF4-4501-A6E9-02A60DCBAE4C
60F75C7C-1AE3-417B-82C8-14D456542CD7
8DE0168D-01D3-4502-9E59-10D665CEBCB2


希望对以后的人有所帮助:)

评论


看起来很棒,已添加到我的列表中!

–穆斯塔法·埃尔多安(Mustafa Erdogan)
19年1月28日在23:24

#6 楼

此函数将使用数字和字母生成随机密钥:

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

    for ($i = 0; $i < $length; $i++) {
        $key .= $keys[array_rand($keys)];
    }

    return $key;
}

echo random_string(50);


示例输出:

zsd16xzv3jsytnp87tk7ygv73k8zmr0ekh6ly7mxaeyeh46oe8


评论


过了一会儿你得到相同的数字

–Sharpless512
2014年2月25日15:31

这不是完全随机的,因为它不会重复出现相同的字符。

– EricP
2014年12月19日在17:32

#7 楼

我使用这种单线格式:

base64_encode(openssl_random_pseudo_bytes(3 * ($length >> 2)));


其中length是所需字符串的长度(可被4整除,否则将四舍五入到最接近的数字可被4整除) )

评论


它并不总是仅返回字母数字字符。它可以包含==之类的字符

–user3459110
14-10-26在8:15

如果在URL中使用,则可以改用base64url_encode,请参见此处:php.net/manual/en/function.base64-encode.php

– Paul
17 Mar 6 '17 at 16:49

#8 楼

使用下面的代码生成11个字符的随机数,或根据您的要求更改数字。

$randomNum=substr(str_shuffle("0123456789abcdefghijklmnopqrstvwxyz"), 0, 11);


,或者我们可以使用自定义函数生成随机数

 function randomNumber($length){
     $numbers = range(0,9);
     shuffle($numbers);
     for($i = 0;$i < $length;$i++)
        $digits .= $numbers[$i];
     return $digits;
 }

 //generate random number
 $randomNum=randomNumber(11);


评论


这永远不会生成字符串“ aaaaaaaaaaa”或“ aaaaabbbbbb”。它只是选择字母随机排列的子字符串。长11个字符的字符串仅具有V(11,35)= 288个可能的值。如果您期望字符串是唯一的,请不要使用它!

– kdojeteri
17年5月5日在21:55

@Peping然后执行substr(str_shuffle(str_repeat('0123456789abcdefghijklmnopqrstvwxyz',36)),0,11);

–蒂姆·霍尔曼(Tim Hallman)
19年3月18日在22:43

谢谢你的好功能! '0123456789abcdefghijklmnopqrstvwxyzABCDEFGHIJKLMNOPQRSTVWXYZ〜!@#$%^&*()_ +-= <>,。?/'-分机号。 str_shuffle范围。

–vitali_y
11月10日17:09



#9 楼


使用您喜欢的随机数生成器
生成随机数
乘以并除以
得到与您的字符数匹配的数字
代码字母

您的代码字母中获得该索引处的项目。
从1)开始重复,直到达到所需长度为止
想要

例如使用伪代码

int myInt = random(0, numcharacters)
char[] codealphabet = 'ABCDEF12345'
char random = codealphabet[i]
repeat until long enough


评论


从理论上讲,这难道不会多次产生相同的字符串吗?

– frezq
19年1月29日在15:06

#10 楼

对于真正随机的字符串,您可以使用

<?php

echo md5(microtime(true).mt_Rand());


输出:

40a29479ec808ad4bcff288a48a25d5c


,即使尝试在完全相同的时间多次生成字符串,您将获得不同的输出。

#11 楼

这是一个简单的函数,可让您生成包含字母和数字(字母数字)的随机字符串。您还可以限制字符串的长度。
这些随机字符串可用于多种用途,包括:推荐代码,促销代码,优惠券代码。
函数依赖于以下PHP函数:
base_convert, sha1,uniqid,mt_rand

function random_code($length)
{
  return substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, $length);
}

echo random_code(6);

/*sample output
* a7d9e8
* 3klo93
*/


#12 楼

尝试生成随机密码时,您尝试执行以下操作:


首先生成一组加密安全的随机字节
第二次将这些随机字节转换为可打印的字符串

现在,有多种方法可以在php中生成随机字节,如:

$length = 32;

//PHP 7+
$bytes= random_bytes($length);

//PHP < 7
$bytes= openssl_random_pseudo_bytes($length);


然后您要将这些随机字节转换为可打印的字符串:

您可以使用bin2hex:

$string = bin2hex($bytes);


或base64_encode:

$string = base64_encode($bytes);


但是,请注意,如果您使用base64,则无法控制字符串的长度。
您可以使用bin2hex来做到这一点,使用32个字节将变成一个64个字符的字符串。
但它只能像因此,即使在EVEN字符串中也是如此。

所以基本上您可以这样做:

$length = 32;

if(PHP_VERSION>=7){
    $bytes= random_bytes($length);
}else{
    $bytes= openssl_random_pseudo_bytes($length);
} 

$string = bin2hex($bytes);


#13 楼

这是为您提供的终极独特ID生成器。

<?php
$d=date ("d");
$m=date ("m");
$y=date ("Y");
$t=time();
$dmt=$d+$m+$y+$t;    
$ran= rand(0,10000000);
$dmtran= $dmt+$ran;
$un=  uniqid();
$dmtun = $dmt.$un;
$mdun = md5($dmtran.$un);
$sort=substr($mdun, 16); // if you want sort length code.

echo $mdun;
?>


您可以根据自己的喜好回显任何'var'作为您的ID。但是$ mdun更好,您可以将md5替换为sha1以获取更好的代码,但这会很长,您可能不需要。

谢谢。

评论


在这种情况下,可以通过随机编写的代码来保证随机性,对吗?

–丹尼尔·库卡尔(Daniel Kucal)
16年1月16日在14:27

是的,这是一种“随机”,因为它是非标准的,晦涩的代码。这使得难以预测...但实际上并非使用不安全的部分来生成随机数。因此,从技术上讲,结果不是随机或安全的。

– Philipp
16年7月5日在18:06

#14 楼

这是我使用的:

md5(time() . rand());    
// Creates something like 0c947c3b1047334f5bb8a3b7adc1d97b


#15 楼

function random_string($length = 8) {
    $alphabets = range('A','Z');
    $numbers = range('0','9');
    $additional_characters = array('_','=');
    $final_array = array_merge($alphabets,$numbers,$additional_characters);
       while($length--) {
      $key = array_rand($final_array);

      $password .= $final_array[$key];
                        }
  if (preg_match('/[A-Za-z0-9]/', $password))
    {
     return $password;
    }else{
    return  random_string();
    }

 }


#16 楼

在阅读了前面的示例后,我想到了:

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,AZ]复制了十倍并对其进行了随机排序substr()更具创造性的起点:)
您可以将[az]和其他元素添加到数组中,或多或少重复,比我更有创造力

#17 楼

我喜欢在处理验证链接时使用哈希键。我建议使用microtime并使用MD5进行散列,因为没有理由说密钥应该相同,因为它是基于microtime散列的。


$key = md5(rand());
$key = md5(microtime());


#18 楼

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

}

?>


以上函数将为您生成一个长度为11个字符的随机字符串。

#19 楼

如果要在PHP中生成唯一的字符串,请尝试以下操作。

md5(uniqid().mt_rand());


uniqid()-它将生成唯一的字符串。此函数将基于时间戳的唯一标识符作为字符串返回。

mt_rand()-生成随机数。

md5()-它将生成哈希字符串。

#20 楼

斯科特,是的,您写得很好,是很好的解决方案!谢谢。

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

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;
}


请看一下,让我知道我是否可以做任何改进。谢谢

#21 楼

这是我在一个项目中使用的东西,它工作得很好,并且会生成一个唯一的随机令牌:

$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;


请注意,我将时间戳记乘以三给任何想知道此令牌如何生成的用户带来混乱;)

我希望它会有所帮助:)

评论


“请注意,我将时间戳记乘以三以使任何想知道此令牌如何生成的用户都感到困惑。”听起来像通过混淆产生的安全性stackoverflow.com/questions/533965/…

–哈里
17年4月6日在9:14

这不是“通过混淆实现的安全性”,因为时间戳会被添加到生成的随机字符串中,而这就是我们返回的实际最终字符串;)

– Fery Kaszoni
18-10-23在11:17

#22 楼

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

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


#23 楼

我们可以使用这两行代码来生成唯一的字符串,这些字符串已经测试了大约10000000次迭代

  $sffledStr= str_shuffle('abscdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_-+');
    $uniqueString = md5(time().$sffledStr);


#24 楼

我总是使用此函数来生成自定义的随机字母数字字符串...希望获得帮助。

<?php
  function random_alphanumeric($length) {
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12345689';
    $my_string = '';
    for ($i = 0; $i < $length; $i++) {
      $pos = mt_rand(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;


它会生成两个唯一的字符串:

qsBDs4JOoVRfFxyLAOGECYIsWvpcpMzAO9pypwwssqPKeAmYLOi

Ti3kE1WfGgTNxQVXtbNNbhhvvapnaUfGMVJecHkUjHbuCb85pF
<

#25 楼

一种简单的解决方案是通过丢弃非字母数字字符将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);
}


#26 楼

我相信所有现有想法的问题在于它们可能是唯一的,但不一定是唯一的(如Dariusz Walczak对loletech的答复所指出)。我有一个实际上是独一无二的解决方案。它要求您的脚本具有某种内存。对我来说,这是一个SQL数据库。您也可以只写一个文件到某处。有两种实现:

第一种方法:具有两个字段,而不是提供唯一性的1个字段。第一个字段是一个ID号码,该号码不是随机的而是唯一的(第一个ID为1,第二个为2 ...)。如果使用的是SQL,则只需使用AUTO_INCREMENT属性定义ID字段。第二个字段不是唯一的,而是随机的。这可以通过人们已经提到的任何其他技术来生成。斯科特的主意不错,但是md5很方便,并且对于大多数用途来说可能足够好:

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


第二种方法:基本上是相同的主意,但最初选择的字符串数最大那将永远产生。这可能是一个非常大的数字,例如一万亿美元。然后执行相同的操作,生成一个ID,但是将其填充零以使所有ID都是相同的位数。然后,只需将ID与随机字符串连接即可。对于大多数用途来说,它是足够随机的,但是ID部分将确保它也是唯一的。

#27 楼

通过消除不必要的循环来简化上面的Scotts代码,这会严重减慢速度,并且使其比仅一次调用openssl_random_pseudo_bytes更加安全。

function crypto_rand_secure($min, $max)
{
 $range = $max - $min;
 if ($range < 1) return $min; // not so random...
 $log = ceil(log($range, 2));
 $bytes = (int) ($log / 8) + 1; // length in bytes
 $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
 return $min + $rnd%$range;
}

function getToken($length)
{
 return bin2hex(openssl_random_pseudo_bytes($length)
}


评论


不幸的是,我不同意这是crypto_rand_secure函数的适当简化。循环是必要的,这样可以避免在取随机生成的数字的结果,然后将其修改为确实会划分范围的数字时发生模偏差。 zh.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Modulo_bias。通过使用循环抛出范围之外的任何数字并选择一个新数字,可以避免这种偏差。 stackoverflow.com/questions/10984974/…

–斯科特
16年6月2日在12:44

@Scott,那个帖子是关于rand()的。但是,在这种情况下,openssl_random_pseudo_bytes已经对其进行了处理,因此不需要循环。

–马克
16年2月2日在13:53

gist.github.com/anonymous/87e3ae6fd3320447f46b973e75c2ea85-请运行此脚本。您将看到结果的分布不是随机的。 0-55之间的数字比56-100之间的数字高。 openssl不会导致这种情况,这是因为return语句中的模偏差。 100(范围)不能平均分割255(1字节)并导致这些结果。我的最大问题是您声明该函数与使用循环一样安全,这是错误的。您对crypto_rand_secure的实现不是加密安全的,并且具有误导性。

–斯科特
16年2月2日在15:08

@Scott,我们在这里讲题,这与最后一个return语句无关,它也可以从openssl_random_pseudo_bytes的返回值逐步计算而无需使用模数。我要说的是,一旦openssl_random_pseudo_bytes返回随机字节,则不需要循环。通过制作循环,您并没有使其变得更安全,而只是使其变得缓慢。

–马克
16年6月2日在16:13

此答案传播错误的信息。您是正确的,该循环不会使openssl更加安全,但是更重要的是,它并没有使它变得不那么安全,此答案可以。上面的代码采用了一个非常好的随机数,并通过将其偏向较小的数字来对其进行处理。如果您在回答中放一个免责声明,说明它更快(通过删除循环),但它不是CSPRNG,则可以解决此问题。请热爱加密,让用户知道此函数的输出可能存在偏差,而不是等效的解决方案。循环不仅使您烦恼。

–斯科特
16年6月2日在17:44