我想生成忘记密码的标识符。我读过我可以通过使用带有mt_rand()的时间戳来做到这一点,但是有些人说时间戳每次都不是唯一的。所以我在这里有点困惑。我可以使用时间戳吗?
问题
生成自定义长度的随机/唯一标记的最佳做法是什么?
我知道这里有很多问题,但是在阅读了不同的人的不同意见后,我变得更加困惑。
答案 0 :(得分:130)
在PHP中,使用random_bytes()
。原因:您正在寻找获取密码提醒令牌的方法,如果是一次性登录凭据,那么您实际上有一个要保护的数据(即整个用户帐户)
所以,代码如下:
//$length = 78 etc
$token = bin2hex(random_bytes($length));
更新:此答案的 previous versions指的是uniqid()
,如果存在安全问题且不仅是唯一性,那么这是不正确的。 uniqid()
基本上只是microtime()
的一些编码。有一些简单的方法可以准确预测服务器上的microtime()
。攻击者可以发出密码重置请求,然后尝试使用几个可能的令牌。如果使用more_entropy,这也是可能的,因为额外的熵同样很弱。感谢@NikiC和@ScottArciszewski指出这一点。
有关详细信息,请参阅
答案 1 :(得分:67)
这回答了“最佳随机”请求:
来自Security.StackExchange的Adi's answer 1 有一个解决方案:
确保您拥有OpenSSL支持,并且这个单行
永远不会出错$token = bin2hex(openssl_random_pseudo_bytes(16));
1. Adi,2018年11月12日,Celeritas,“为确认电子邮件生成一个不可饶恕的令牌”,2013年9月20日7:06,https://security.stackexchange.com/a/40314/
答案 2 :(得分:53)
接受的答案的早期版本(md5(uniqid(mt_rand(), true))
)是不安全的,只提供大约2 ^ 60个可能的输出 - 大约在一周的时间内对于低预算的攻击者进行暴力搜索:
mt_rand()
is predictable(最多只能添加31位熵)uniqid()
only adds up to 29 bits of entropy md5()
不添加熵,它只是确定性地混合由于56-bit DES key can be brute-forced in about 24 hours,并且平均情况下将具有大约59位的熵,我们可以计算2 ^ 59/2 ^ 56 =大约8天。根据此令牌验证的实施方式,it might be possible to practically leak timing information and infer the first N bytes of a valid reset token。
由于问题是关于“最佳实践”的问题,因此打开...
我想为忘记密码生成标识符
...我们可以推断出这个令牌具有隐含的安全性要求。当您向随机数生成器添加安全要求时,最佳做法是始终使用加密安全伪随机数生成器(缩写为CSPRNG)。
在PHP 7中,您可以使用bin2hex(random_bytes($n))
(其中$n
是大于15的整数)。
在PHP 5中,您可以使用random_compat
来公开相同的API。
或者,如果您安装了bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM))
,则ext/mcrypt
。另一个好的单行是bin2hex(openssl_random_pseudo_bytes($n))
。
从我之前的secure "remember me" cookies in PHP工作开始,减轻上述时间泄漏(通常由数据库查询引入)的唯一有效方法是将查找与验证分开。
如果你的表看起来像这样(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发出密码重置令牌时,将两个值都发送给用户,将选择器和随机令牌的SHA-256哈希存储在数据库中。使用选择器获取哈希和用户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('Y-m-d\TH:i:s')
]);
验证用户提供的重置令牌:
$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.
}
这些代码片段不是完整的解决方案(我避开了输入验证和框架集成),但它们应该作为做什么的一个例子。
答案 3 :(得分:6)
您还可以使用DEV_RANDOM,其中128 = 1/2生成的令牌长度。下面的代码生成256个令牌。
$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));
答案 4 :(得分:1)
每当您需要非常随机的令牌时,这可能会有所帮助
<?php
echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
?>
答案 5 :(得分:-7)
您可以使用
echo str_shuffle('ASGDHFfdgfdre5475433fd');