为忘记密码生成随机令牌的最佳做法

kee*_*een 90 php security random timestamp token

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

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

我知道这里有很多问题,但是在阅读了不同的人的不同意见后,我变得更加困惑.

Alm*_* Do 139

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

所以,代码如下:

//$length = 78 etc
$token = bin2hex(random_bytes($length));
Run Code Online (Sandbox Code Playgroud)

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

有关详细信息,请参阅

  • 请注意,[`random_bytes()`](http://php.net/manual/en/function.random-bytes.php)仅在PHP7时可用.对于旧版本,@yesitsme的答案似乎是最好的选择. (20认同)
  • @GeraldSchneider或[random_compat](https://github.com/paragonie/random_compat),这是获得最多同行评审的这些功能的polyfill;) (3认同)
  • @gordie将长度设置为32,每个字节为2个十六进制字符 (2认同)

We *_*lio 70

这回答了"最佳随机"请求:

来自Security.StackExchange的Adi的答案1有一个解决方案:

确保你拥有OpenSSL支持,并且你永远不会出错这个单线程

$token = bin2hex(openssl_random_pseudo_bytes(16));
Run Code Online (Sandbox Code Playgroud)

1. Adi,2018年11月12日,Celeritas,"为确认电子邮件生成一个不可饶恕的令牌",2013年9月20日7:06,https: //security.stackexchange.com/a/40314/

  • `openssl_random_pseudo_bytes($ length)` - 支持:PHP 5> = 5.3.0,................................ ..........................(对于PHP 7及更高版本,使用`random_bytes($ length)`)........ ..................................(对于5.3以下的PHP - 不要使用5.3以下的PHP) (22认同)

Sco*_*ski 54

接受的答案(md5(uniqid(mt_rand(), true)))的早期版本是不安全的,只提供大约2 ^ 60个可能的输出 - 在一个星期的低预算攻击者的蛮力搜索范围内:

由于56位DES密钥可以在大约24小时内强制执行,并且平均情况下将具有大约59位的熵,我们可以计算2 ^ 59/2 ^ 56 =大约8天.根据如何实现此令牌验证,可能实际上泄漏定时信息并推断有效重置令牌的前N个字节.

由于问题是关于"最佳实践",并打开...

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

...我们可以推断出这个令牌具有隐含的安全要求.当您向随机数生成器添加安全性要求时,最佳做法是始终使用加密安全伪随机数生成器(缩写为CSPRNG).


使用CSPRNG

在PHP 7中,您可以使用bin2hex(random_bytes($n))(where $n是大于15的整数).

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

或者,bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM))如果您已ext/mcrypt安装.另一个好的单线是bin2hex(openssl_random_pseudo_bytes($n)).

将查找与验证器分开

从我之前关于PHP中安全"记住我"cookie的工作中提取,减轻上述时间泄漏(通常由数据库查询引入)的唯一有效方法是将查找与验证分开.

如果你的表看起来像这样(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)
);
Run Code Online (Sandbox Code Playgroud)

...你需要再添加一列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)
);
Run Code Online (Sandbox Code Playgroud)

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

示例代码

使用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')
]);
Run Code Online (Sandbox Code Playgroud)

验证用户提供的重置令牌:

$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.
}
Run Code Online (Sandbox Code Playgroud)

这些代码片段不是完整的解决方案(我避开了输入验证和框架集成),但它们应该作为一个例子来做.


Gra*_*m T 7

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

$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));
Run Code Online (Sandbox Code Playgroud)

  • 我建议`MCRYPT_DEV_URANDOM`超过`MCRYPT_DEV_RANDOM`. (4认同)