web-dev-qa-db-fra.com

bonne pratique pour générer des jetons aléatoires pour un mot de passe oublié

Je veux générer l'identifiant du mot de passe oublié. J'ai lu que je pouvais le faire en utilisant timestamp avec mt_Rand (), mais certaines personnes disent que cet horodatage peut ne pas être unique à chaque fois. Donc, je suis un peu confus ici. Puis-je le faire avec l'aide de l'horodatage avec cela?

Question
Quelle est la meilleure pratique pour générer des jetons aléatoires/uniques de longueur personnalisée?

Je sais que beaucoup de questions sont posées ici, mais je suis de plus en plus confus après avoir lu différentes opinions de personnes différentes.

87
keen

En PHP, utilisez random_bytes() . Raison: vous cherchez le moyen d'obtenir un jeton de rappel de mot de passe et, s'il s'agit d'un identifiant de connexion unique, vous avez en réalité une donnée à protéger (compte d'utilisateur complet).

Donc, le code sera comme suit:

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

Mise à jour : versions précédentes de cette réponse faisait référence à uniqid() et est incorrect s'il s'agit d'une question de la sécurité et pas seulement l'unicité. uniqid() est essentiellement juste microtime() avec un encodage. Il existe des moyens simples d’obtenir des prédictions précises de la microtime() sur votre serveur. Un attaquant peut émettre une demande de réinitialisation de mot de passe, puis essayer plusieurs jetons probables. Cela est également possible si vous utilisez more_entropy, car l’entropie supplémentaire est également faible. Merci à @ NikiC et @ ScottArciszewski pour l'avoir signalé.

Pour plus de détails voir

137
Alma Do

Ceci répond à la requête 'best random':

réponse d'Adi1 de Security.StackExchange a une solution pour cela:

Assurez-vous que vous avez le support OpenSSL, et vous ne vous tromperez jamais avec ce one-liner

$token = bin2hex(openssl_random_pseudo_bytes(16));

1. Adi, lun 12 nov 2018, Celeritas, "Génération d'un jeton indiscutable pour les courriels de confirmation", 20 sept. 13 à 7:06, https://security.stackexchange.com/a/40314/

69
yesitsme

La version précédente de la réponse acceptée (md5(uniqid(mt_Rand(), true))) n'est pas sécurisée et n'offre que 2 sorties sur 60 possibles, ce qui correspond bien à la recherche d'une force brute dans environ une semaine pour un attaquant à petit budget:

Puisqu'une clé la touche DES de 56 bits peut être forcée dans environ 24 heures , et qu'un cas moyen aurait environ 59 bits d'entropie, nous pouvons calculer 2 ^ 59/2 ^ 56 = environ 8 jours. En fonction de la manière dont cette vérification de jeton est mise en œuvre, il serait peut-être possible de divulguer des informations temporelles et d'inférer les N premiers octets d'un jeton de réinitialisation valide .

Puisque la question concerne les "meilleures pratiques" et commence par ...

Je veux générer l'identifiant du mot de passe oublié

... nous pouvons en déduire que ce jeton a des exigences de sécurité implicites. Et lorsque vous ajoutez des exigences de sécurité à un générateur de nombre aléatoire, la meilleure pratique consiste à toujours utiliser un générateur de nombre pseudo-aléatoire cryptographique (en abrégé CSPRNG).


Utiliser un CSPRNG

Dans PHP 7, vous pouvez utiliser bin2hex(random_bytes($n)) (où _$n_ est un entier supérieur à 15).

Dans PHP 5, vous pouvez utiliser random_compat pour exposer la même API.

Vous pouvez également utiliser bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM)) si _ext/mcrypt_ est installé. Un autre bon choix est bin2hex(openssl_random_pseudo_bytes($n)).

Séparer la recherche du validateur

Tirant de mon travail précédent sur cookies "Remember Me" en PHP , le seul moyen efficace de limiter la fuite de temps susmentionnée (généralement introduite par la requête de base de données) est de séparer la recherche de la validation.

Si votre table ressemble à ceci (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)
);
_

... vous devez ajouter une colonne supplémentaire, selector, comme suit:

_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)
);
_

Utiliser un CSPRNG Lorsqu'un jeton de réinitialisation de mot de passe est émis, envoyez les deux valeurs à l'utilisateur, stockez le sélecteur et un hachage SHA-256 du jeton aléatoire dans la base de données. Utilisez le sélecteur pour saisir le hachage et l'ID utilisateur, calculez le hachage SHA-256 du jeton fourni par l'utilisateur avec celui stocké dans la base de données à l'aide de hash_equals() .

Exemple de code

Générer un jeton de réinitialisation dans PHP 7 (ou 5.6 avec random_compat) avec PDO:

_$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')
]);
_

Vérification du jeton de réinitialisation fourni par l'utilisateur:

_$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.
}
_

Ces extraits de code ne sont pas des solutions complètes (j'ai évité la validation des entrées et les intégrations de framework), mais ils devraient servir d'exemple pour ce qu'il faut faire.

54
Scott Arciszewski

Vous pouvez également utiliser DEV_RANDOM, où 128 = 1/2 de la longueur du jeton généré. Le code ci-dessous génère 256 jetons.

$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));
6
Graham T

Cela peut être utile chaque fois que vous avez besoin d’un jeton très aléatoire.

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