php 为忘记密码生成随机令牌的最佳实践
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18910814/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
best practice to generate random token for forgot password
提问by keen
I want to generate identifier for forgot password . I read i can do it by using timestamp with mt_rand(), but some people are saying that time stamp might not be unique every time. So i am bit of confused here. Can i do it with using time stamp with this ?
我想为忘记密码生成标识符。我读到我可以通过将时间戳与 mt_rand() 一起使用来做到这一点,但有些人说时间戳可能不是每次都是唯一的。所以我在这里有点困惑。我可以使用时间戳来做到这一点吗?
Question
What's best practice to generate random/unique tokens of custom length?
问题
生成自定义长度的随机/唯一标记的最佳实践是什么?
I know there are lot of questions asked around here but i am getting more confused after reading different opinion from the different people.
我知道这里有很多问题,但在阅读了不同人的不同意见后,我越来越困惑。
回答by Alma Do
In PHP, use random_bytes()
. Reason: your are seeking the way to get a password reminder token, and, if it is a one-time login credentials, then you actually have a data to protect (which is - whole user account)
在 PHP 中,使用random_bytes()
. 原因:您正在寻找获取密码提醒令牌的方法,并且,如果它是一次性登录凭据,那么您实际上有一个数据需要保护(即 - 整个用户帐户)
So, the code will be as follows:
因此,代码将如下所示:
//$length = 78 etc
$token = bin2hex(random_bytes($length));
Update: previous versionsof this answer was referring to uniqid()
and that is incorrect if there is a matter of security and not only uniqueness. uniqid()
is essentially just microtime()
with some encoding. There are simple ways to get accurate predictions of the microtime()
on your server. An attacker can issue a password reset request and then try through a couple of likely tokens. This is also possible if more_entropy is used, as the additional entropy is similarly weak. Thanks to @NikiCand @ScottArciszewskifor pointing this out.
更新:此答案的先前版本所指的uniqid()
是安全性问题而不仅仅是唯一性问题,这是不正确的。uniqid()
本质上只是microtime()
一些编码。有一些简单的方法可以准确预测microtime()
服务器上的 。攻击者可以发出密码重置请求,然后尝试通过几个可能的令牌。如果使用 more_entropy,这也是可能的,因为附加熵同样很弱。感谢@NikiC和@ScottArciszewski指出这一点。
For more details see
有关更多详细信息,请参阅
回答by YesItsMe
This answers the 'best random' request:
这回答了“最佳随机”请求:
Adi's answer1from Security.StackExchange has a solution for this:
来自 Security.StackExchange 的Adi 的回答1对此有一个解决方案:
Make sure you have OpenSSL support, and you'll never go wrong with this one-liner
$token = bin2hex(openssl_random_pseudo_bytes(16));
确保你有 OpenSSL 支持,你永远不会出错的单线
$token = bin2hex(openssl_random_pseudo_bytes(16));
1. Adi, Mon Nov 12 2018, Celeritas, "Generating an unguessable token for confirmation e-mails", Sep 20 '13 at 7:06, https://security.stackexchange.com/a/40314/
1. Adi,2018 年 11 月 12 日星期一,Celeritas,“为确认电子邮件生成不可猜测的令牌”,2013 年 9 月 20 日 7:06,https://security.stackexchange.com/a/40314/
回答by Scott Arciszewski
The earlier version of the accepted answer (md5(uniqid(mt_rand(), true))
) is insecure and only offers about 2^60 possible outputs -- well within the range of a brute force search in about a week's time for a low-budget attacker:
已接受答案的早期版本 ( md5(uniqid(mt_rand(), true))
) 不安全,仅提供大约 2^60 个可能的输出——对于低预算攻击者来说,这完全在大约一周的时间内进行蛮力搜索的范围内:
mt_rand()
is predictable(and only adds up to 31 bits of entropy)uniqid()
only adds up to 29 bits of entropymd5()
doesn't add entropy, it just mixes it deterministically
mt_rand()
是可预测的(并且最多只能添加 31 位的熵)uniqid()
最多只添加 29 位熵md5()
不添加熵,它只是确定性地混合它
Since a 56-bit DES key can be brute-forced in about 24 hours, and an average case would have about 59 bits of entropy, we can calculate 2^59 / 2^56 = about 8 days. Depending on how this token verification is implemented, it might be possible to practically leak timing information and infer the first N bytes of a valid reset token.
由于一个56 位的 DES 密钥可以在大约 24 小时内被暴力破解,并且平均情况下会有大约 59 位的熵,我们可以计算出 2^59 / 2^56 = 大约 8 天。根据此令牌验证的实现方式,实际上可能会泄漏计时信息并推断有效重置令牌的前 N 个字节。
Since the question is about "best practices" and opens with...
由于问题是关于“最佳实践”并以...
I want to generate identifier for forgot password
我想为忘记密码生成标识符
...we can infer that this token has implicit security requirements. And when you add security requirements to a random number generator, the best practice is to always use a cryptographically secure pseudorandom number generator(abbreviated CSPRNG).
...我们可以推断该令牌具有隐含的安全要求。当您向随机数生成器添加安全要求时,最佳做法是始终使用加密安全的伪随机数生成器(缩写为 CSPRNG)。
Using a CSPRNG
使用 CSPRNG
In PHP 7, you can use bin2hex(random_bytes($n))
(where $n
is an integer larger than 15).
在 PHP 7 中,您可以使用bin2hex(random_bytes($n))
(where$n
是一个大于 15 的整数)。
In PHP 5, you can use random_compat
to expose the same API.
在 PHP 5 中,您可以使用random_compat
公开相同的 API。
Alternatively, bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM))
if you have ext/mcrypt
installed. Another good one-liner is bin2hex(openssl_random_pseudo_bytes($n))
.
或者,bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM))
如果您已ext/mcrypt
安装。另一个好的单线是bin2hex(openssl_random_pseudo_bytes($n))
.
Separating the Lookup from the Validator
将查找与验证器分开
Pulling from my previous work on secure "remember me" cookies in PHP, the only effective way to mitigate the aforementioned timing leak (typically introduced by the database query) is to separate the lookup from the validation.
从我之前关于PHP 中安全“记住我”cookie 的工作中提取出来,缓解上述时间泄漏(通常由数据库查询引入)的唯一有效方法是将查找与验证分开。
If your table looks like this (MySQL)...
如果您的表看起来像这样(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)
);
... you need to add one more column, selector
, like so:
...您需要再添加一列,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)
);
Use a CSPRNG When a password reset token is issued, send both values to the user, store the selector and a SHA-256 hash of the random token in the database. Use the selector to grab the hash and User ID, calculate the SHA-256 hash of the token the user provides with the one stored in the database using hash_equals()
.
使用 CSPRNG 发出密码重置令牌时,将这两个值发送给用户,将选择器和随机令牌的 SHA-256 散列存储在数据库中。使用选择器获取哈希值和用户 ID,计算用户提供的令牌的 SHA-256 哈希值和使用hash_equals()
.
Example Code
示例代码
Generating a reset token in PHP 7 (or 5.6 with random_compat) with PDO:
使用 PDO 在 PHP 7(或 5.6 with random_compat)中生成重置令牌:
$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')
]);
Verifying the user-provided reset token:
验证用户提供的重置令牌:
$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.
}
These code snippets are not complete solutions (I eschewed the input validation and framework integrations), but they should serve as an example of what to do.
这些代码片段不是完整的解决方案(我避开了输入验证和框架集成),但它们应该作为一个例子来说明要做什么。
回答by Graham T
You can also use DEV_RANDOM, where 128 = 1/2 the generated token length. Code below generates 256 token.
您还可以使用 DEV_RANDOM,其中 128 = 1/2 生成的令牌长度。下面的代码生成 256 个令牌。
$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));
回答by Ir Calif
This may be helpful whenever you need a very very random token
每当您需要非常随机的令牌时,这可能会有所帮助
<?php
echo mb_strtoupper(strval(bin2hex(openssl_random_pseudo_bytes(16))));
?>