为忘记密码生成随机令牌的最佳实践
我想为忘记密码生成标识符。 我读过,我可以通过使用mt_rand()的时间戳来做到这一点,但有些人说,时间戳可能不是唯一的每一次。 所以我有点困惑。 我可以用这个时间戳吗?
题
生成自定义长度的随机/唯一标记的最佳做法是什么?
我知道这里有很多问题,但是在阅读了不同的人的不同意见后,我越来越困惑。
在PHP中,使用random_bytes()
。 原因:您正在寻找获取密码提醒令牌的方式,如果它是一次性login凭据,那么您实际上有一个要保护的数据(即 – 整个用户帐户)
所以,代码如下:
//$length = 78 etc $token = bin2hex(random_bytes($length));
更新 : 这个答案的以前的版本是指uniqid()
,这是不正确的,如果有一个安全问题,而不是唯一性。 uniqid()
基本上就是microtime()
和一些编码。 有很简单的方法可以准确预测服务器上的microtime()
。 攻击者可以发出密码重置请求,然后尝试通过一些可能的令牌。 如果使用more_entropy,这也是可能的,因为额外的熵同样弱。 感谢@NikiC和@ScottArciszewski指出这一点。
欲了解更多详情请参阅
这个答案最好随机
$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/mcrypt
, bin2hex(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');