100 votes

La meilleure pratique pour générer un jeton aléatoire pour réinitialiser le mot de passe

Je veux générer un identifiant pour le mot de passe oublié. J'ai lu que je peux le faire en utilisant un horodatage avec mt_rand(), mais certaines personnes disent que l'horodatage pourrait ne pas être unique à chaque fois. Je suis donc un peu confus ici. Puis-je le faire en utilisant un horodatage de cette manière ?

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

Je sais qu'il y a beaucoup de questions posées ici mais je suis de plus en plus confus après avoir lu différents avis de différentes personnes.

156voto

Alma Do Points 23653

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 de références de connexion à usage unique, alors vous avez en réalité des données à protéger (c'est-à-dire - l'ensemble du compte utilisateur)

Ainsi, le code sera le suivant :

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

Mise à jour : les versions précédentes de cette réponse faisaient référence à uniqid() et c'est incorrect s'il y a une question de sécurité et pas seulement d'unicité. uniqid() est essentiellement juste microtime() avec un encodage. Il existe des moyens simples d'obtenir des prévisions précises de la microtime() sur votre serveur. Un attaquant peut émettre une demande de réinitialisation du mot de passe puis essayer à travers quelques jetons probables. Ceci est également possible si more_entropy est utilisé, car l'entropie supplémentaire est également faible. Merci à @NikiC et @ScottArciszewski pour avoir souligné cela.

Pour plus de détails, voir

73voto

Cela répond à la demande de 'meilleur aléatoire':

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

Assurez-vous d'avoir le support d'OpenSSL, et vous ne vous tromperez jamais avec cette ligne de commande

$token = bin2hex(openssl_random_pseudo_bytes(16));

1. Adi, Lun Nov 12 2018, Celeritas, "Générer un jeton introuvable pour les e-mails de confirmation", Sep 20 '13 à 7:06, <a href="https://security.stackexchange.com/a/40314/">https://security.stackexchange.com/a/40314/</a>

58voto

Scott Points 389

La version précédente de la réponse acceptée (md5(uniqid(mt_rand(), true))) est peu sécurisée et n'offre que environ 2^60 sorties possibles - bien dans la plage d'une recherche par force brute en environ une semaine pour un attaquant à petit budget :

Étant donné qu'une clé DES de 56 bits peut être brute-forcée en environ 24 heures, et un cas moyen aurait environ 59 bits d'entropie, nous pouvons calculer 2^59 / 2^56 = environ 8 jours. Selon la façon dont cette vérification de jeton est implémentée, il pourrait être possible de divulguer des informations de timing et d'inférer les premiers N octets d'un jeton de réinitialisation valide.

Étant donné que la question concerne les "meilleures pratiques" et commence par...

Je veux générer un identifiant pour le 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 nombres aléatoires, la meilleure pratique est d'utiliser toujours un générateur de nombres pseudo-aléatoires cryptographiquement sûr (abrégé CSPRNG).


Utilisation d'un CSPRNG

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

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

Alternativement, bin2hex(mcrypt_create_iv($n, MCRYPT_DEV_URANDOM)) si vous avez ext/mcrypt installé. Une autre bonne ligne de commande est bin2hex(openssl_random_pseudo_bytes($n)).

Séparation de la Recherche du Valideur

Puisant dans mon travail précédent sur les cookies "se souvenir de moi" sécurisés en PHP, la seule façon efficace de pallier la fuite de timing susmentionnée (généralement introduite par la requête à la 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 ceci :

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

Utilisez 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 récupérer le hachage et l'identifiant utilisateur, calculez le hachage SHA-256 du jeton fourni par l'utilisateur avec celui stocké dans la base de données en utilisant hash_equals().

Exemple de Code

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

$selector = bin2hex(random_bytes(8));
$token = random_bytes(32);

$urlToEmail = 'http://exemple.com/reset.php?'.http_build_query([
    'selector' => $selector,
    'validator' => bin2hex($token)
]);

$expires = new DateTime('NOW');
$expires->add(new DateInterval('PT01H')); // 1 heure

$stmt = $pdo->prepare("INSERT INTO account_recovery (userid, selector, token, expires) VALUES (:userid, :selector, :token, :expires);");
$stmt->execute([
    'userid' => $userId, // définir cela ailleurs !
    'selector' => $selector,
    'token' => hash('sha256', $token),
    'expires' => $expires->format('Y-m-d\TH:i:s')
]);

Vérifiant le 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'])) {
        // Le jeton de réinitialisation est valide. Authentifier l'utilisateur.
    }
    // Supprimez le jeton de la base de données indépendamment du succès ou de l'échec.
}

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 de ce qu'il faut faire.

7voto

Graham T Points 938

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

$token = bin2hex(mcrypt_create_iv(128, MCRYPT_DEV_RANDOM));

2voto

Ir Calif Points 428

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X