为忘记密码生成随机令牌的最佳实践

我想为忘记密码生成标识符。 我读过,我可以通过使用mt_rand()的时间戳来做到这一点,但有些人说,时间戳可能不是唯一的每一次。 所以我有点困惑。 我可以用这个时间戳吗?


生成自定义长度的随机/唯一标记的最佳做法是什么?

我知道这里有很多问题,但是在阅读了不同的人的不同意见后,我越来越困惑。

在PHP中,使用random_bytes() 。 原因:您正在寻找获取密码提醒令牌的方式,如果它是一次性login凭据,那么您实际上有一个要保护的数据(即 – 整个用户帐户)

所以,代码如下:

 //$length = 78 etc $token = bin2hex(random_bytes($length)); 

更新这个答案的以前的版本是指uniqid() ,这是不正确的,如果有一个安全问题,而不是唯一性。 uniqid()基本上就是microtime()和一些编码。 有很简单的方法可以准确预测服务器上的microtime() 攻击者可以发出密码重置请求,然后尝试通过一些可能的令牌。 如果使用more_entropy,这也是可能的,因为额外的熵同样弱。 感谢@NikiC和@ScottArciszewski指出这一点。

欲了解更多详情请参阅

https://security.stackexchange.com/questions/40310/generating-an-unguesable-token-for-confirmation-e-mails

这个答案最好随机

 $token = bin2hex(openssl_random_pseudo_bytes(16)); 

被接受的答案的早期版本( md5(uniqid(mt_rand(), true)) )是不安全的,只提供了2 ^ 60个可能的输出 – 在一个星期左右的时间内, – 预算攻击者:

  • mt_rand()是可预测的 (并且只加起来31位的熵)
  • uniqid()只能加起来29位的熵
  • md5()不添加熵,它只是确定性地混合它

由于一个56位的DES密钥可能在24小时内遭到强制攻击 ,平均情况下会有大约59位的熵,我们可以计算出2 ^ 59/2 ^ 56 = 8天左右。 根据这个令牌validation是如何实现的, 实际上可能泄漏定时信息并推断有效的复位令牌的前N个字节 。

由于这个问题是关于“最佳实践”,并打开…

我想为忘记密码生成标识符

…我们可以推断出这个标记具有隐含的安全性要求。 而且,当您将安全要求添加到随机数生成器时,最佳做法是始终使用密码安全的伪随机数生成器 (缩写为CSPRNG)。


使用CSPRNG

在PHP 7中,可以使用bin2hex(random_bytes($n)) (其中$n是一个大于15的整数)。

在PHP 5中,您可以使用random_compat来公开相同的API。

另外,如果你安装了ext/mcryptbin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM)) 。 另一个好的bin2hex(openssl_random_pseudo_bytes($n))bin2hex(openssl_random_pseudo_bytes($n))

将查找从validation器中分离出来

从我以前在PHP中使用安全“记住我”cookie的工作中 ,缓解上述定时泄漏(通常由数据库查询引入)的唯一有效方法是将查找与validation分开。

如果你的表看起来像这样(MySQL)…

 CREATE TABLE account_recovery ( id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT userid INTEGER(11) UNSIGNED NOT NULL, token CHAR(64), expires DATETIME, PRIMARY KEY(id) ); 

…你需要添加一个更多的列, selector ,如下所示:

 CREATE TABLE account_recovery ( id INTEGER(11) UNSIGNED NOT NULL AUTO_INCREMENT userid INTEGER(11) UNSIGNED NOT NULL, selector CHAR(16), token CHAR(64), expires DATETIME, PRIMARY KEY(id), KEY(selector) ); 

使用CSPRNG发出密码重置令牌时,将这两个值发送给用户,将select器和随机令牌的SHA-256哈希值存储在数据库中。 使用select器来获取哈希和用户ID,使用hash_equals()计算用户提供的令牌的SHA-256哈希。

示例代码

使用PDO在PHP 7(或使用random_compat的5.6)中生成重置标记:

 $selector = bin2hex(random_bytes(8)); $token = random_bytes(32); $urlToEmail = 'http://example.com/reset.php?'.http_build_query([ 'selector' => $selector, 'validator' => bin2hex($token) ]); $expires = new DateTime('NOW'); $expires->add(new DateInterval('PT01H')); // 1 hour $stmt = $pdo->prepare("INSERT INTO account_recovery (userid, selector, token, expires) VALUES (:userid, :selector, :token, :expires);"); $stmt->execute([ 'userid' => $userId, // define this elsewhere! 'selector' => $selector, 'token' => hash('sha256', $token), 'expires' => $expires->format('Ymd\TH:i:s') ]); 

validation用户提供的重置令牌:

 $stmt = $pdo->prepare("SELECT * FROM account_recovery WHERE selector = ? AND expires >= NOW()"); $stmt->execute([$selector]); $results = $stmt->fetchAll(PDO::FETCH_ASSOC); if (!empty($results)) { $calc = hash('sha256', hex2bin($validator)); if (hash_equals($calc, $results[0]['token'])) { // The reset token is valid. Authenticate the user. } // Remove the token from the DB regardless of success or failure. } 

这些代码片段并不是完整的解决scheme(我避开了inputvalidation和框架集成),但它们应该成为做什么的一个例子。

您也可以使用DEV_RANDOM,其中128 = 1/2生成的令牌长度。 下面的代码生成256令牌。

 $token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM)); 

当你需要一个非常随机的标记时,这可能是有用的

 <?php echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16)))); ?> 

你可以使用echo str_shuffle('ASGDHFfdgfdre5475433fd');