499 votes

Comment générer une chaîne alphanumérique aléatoire et unique ?

Comment serait-il possible de générer une chaîne aléatoire et unique à l'aide de chiffres et de lettres pour l'utiliser dans un lien de vérification ? Par exemple, lorsque vous créez un compte sur un site Web et qu'il vous envoie un courriel contenant un lien, vous devez cliquer sur ce lien pour vérifier votre compte.

Comment puis-je en générer un en utilisant PHP ?

1 votes

Tout ce dont vous avez besoin, ce sont des chaînes de caractères et des nombres aléatoires uniformément distribués.

12 votes

Hé Andrew, tu devrais choisir Scott comme réponse correcte. Scott utilise le générateur de nombres psudo-aléatoires à sécurité cryptographique (CSPRNG) d'OpenSSL, qui choisira la source d'entropie la plus sûre en fonction de votre plate-forme.

3 votes

Si vous lisez ceci après 2015, s'il vous plaît, regardez ici : paragonie.com/blog/2015/07/ La plupart des réponses sont plus ou moins erronées...

682voto

Scott Points 2287

La bibliothèque standard de PHP 7 fournit la fonction random_bytes($length) qui génèrent des octets pseudo-aléatoires cryptographiquement sûrs.

Exemple :

$bytes = random_bytes(20);
var_dump(bin2hex($bytes));

L'exemple ci-dessus produira quelque chose de similaire :

string(40) "5fe69c95ed70a9869d9f9af7d8400a6673bb9ce9"

Plus d'informations : http://php.net/manual/en/function.random-bytes.php

PHP 5 (obsolète)

Je cherchais justement comment résoudre ce même problème, mais je veux aussi que ma fonction crée un jeton qui puisse être utilisé pour récupérer le mot de passe. Cela signifie que je dois limiter la capacité du jeton à être deviné. Parce que uniqid est basé sur le temps, et selon php.net "la valeur de retour est peu différente de microtime()", uniqid ne répond pas aux critères. PHP recommande d'utiliser openssl_random_pseudo_bytes() au lieu de générer des jetons cryptographiquement sécurisés.

Une réponse rapide, courte et précise est la suivante :

bin2hex(openssl_random_pseudo_bytes($bytes))

qui générera une chaîne aléatoire de caractères alphanumériques de longueur = $bytes * 2. Malheureusement, cette chaîne n'a qu'un alphabet de [a-f][0-9] mais ça marche.


Voici la fonction la plus puissante que j'ai pu créer et qui répond aux critères (il s'agit d'une version implémentée de la réponse d'Erik).

function crypto_rand_secure($min, $max)
{
    $range = $max - $min;
    if ($range < 1) return $min; // not so random...
    $log = ceil(log($range, 2));
    $bytes = (int) ($log / 8) + 1; // length in bytes
    $bits = (int) $log + 1; // length in bits
    $filter = (int) (1 << $bits) - 1; // set all lower bits to 1
    do {
        $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
        $rnd = $rnd & $filter; // discard irrelevant bits
    } while ($rnd > $range);
    return $min + $rnd;
}

function getToken($length)
{
    $token = "";
    $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
    $codeAlphabet.= "0123456789";
    $max = strlen($codeAlphabet); // edited

    for ($i=0; $i < $length; $i++) {
        $token .= $codeAlphabet[crypto_rand_secure(0, $max-1)];
    }

    return $token;
}

crypto_rand_secure($min, $max) fonctionne comme un substitut à la rand() ou mt_rand . Il utilise openssl_random_pseudo_bytes pour aider à créer un nombre aléatoire entre $min et $max.

getToken($length) crée un alphabet à utiliser dans le jeton et crée ensuite une chaîne de longueur $length .

Source : http://us1.php.net/manual/en/function.openssl-random-pseudo-bytes.php#104322

0 votes

J'espère que par "récupération des mots de passe" vous ne voulez pas dire que vous stockez les mots de passe en clair. Si c'est le cas, votre sécurité ici semble inutile.

2 votes

Je suis d'accord. Le stockage des mots de passe en clair est horrible ! (J'utilise blowfish.) Mais après que le jeton soit généré, il permet au détenteur du jeton de réinitialiser le mot de passe, donc le jeton est équivalent au mot de passe tant qu'il est valide (et est traité comme tel).

30 votes

Hé, j'ai combiné votre excellent code avec la fonctionnalité pratique de la méthode Kohana Text::random() et j'ai créé ce gist : gist.github.com/raveren/5555297

346voto

loletech Points 818

Avis de sécurité : Cette solution ne doit pas être utilisée dans les situations où la qualité de votre caractère aléatoire peut affecter la sécurité d'une application. En particulier, rand() et uniqid() ne sont pas des générateurs de nombres aléatoires cryptographiquement sûrs. . Voir Réponse de Scott pour une alternative sécurisée.

Si vous n'en avez pas besoin pour être absolument unique au fil du temps :

md5(uniqid(rand(), true))

Sinon (étant donné que vous avez déjà déterminé un login unique pour votre utilisateur) :

md5(uniqid($your_user_login, true))

101 votes

Les deux méthodes ne garantie unicité - la longueur de l'entrée de la fonction md5 est plus grande que la longueur de sa sortie et selon le fr.wikipedia.org/wiki/Pigeonhole_principle la collision est garantie. D'autre part, plus la population qui s'appuie sur les hachages pour obtenir l'identifiant "unique" est importante, plus la probabilité d'occurrence d'au moins une collision est grande (cf. fr.wikipedia.org/wiki/Problème d'anniversaire ). La probabilité est peut-être minime pour la plupart des solutions, mais elle existe quand même.

14 votes

Il ne s'agit pas d'une méthode sûre pour générer des valeurs aléatoires. Voir la réponse de Scott.

7 votes

Pour les confirmations d'e-mails qui expirent relativement vite, je pense que l'infime possibilité qu'une personne puisse un jour confirmer l'e-mail de quelqu'un d'autre est un risque négligeable. Mais vous pouvez toujours vérifier si le jeton existe déjà dans la base de données après l'avoir créé et en choisir un nouveau si c'est le cas. Cependant, le temps que vous passez à écrire ce bout de code est probablement perdu car il ne sera probablement jamais exécuté.

104voto

Slava Fomin II Points 1141

Version orientée objet de la solution la plus votée

J'ai créé une solution orientée objet basée sur Scott La réponse de la Commission :

<?php

namespace Utils;

/**
 * Class RandomStringGenerator
 * @package Utils
 *
 * Solution taken from here:
 * http://stackoverflow.com/a/13733588/1056679
 */
class RandomStringGenerator
{
    /** @var string */
    protected $alphabet;

    /** @var int */
    protected $alphabetLength;

    /**
     * @param string $alphabet
     */
    public function __construct($alphabet = '')
    {
        if ('' !== $alphabet) {
            $this->setAlphabet($alphabet);
        } else {
            $this->setAlphabet(
                  implode(range('a', 'z'))
                . implode(range('A', 'Z'))
                . implode(range(0, 9))
            );
        }
    }

    /**
     * @param string $alphabet
     */
    public function setAlphabet($alphabet)
    {
        $this->alphabet = $alphabet;
        $this->alphabetLength = strlen($alphabet);
    }

    /**
     * @param int $length
     * @return string
     */
    public function generate($length)
    {
        $token = '';

        for ($i = 0; $i < $length; $i++) {
            $randomKey = $this->getRandomInteger(0, $this->alphabetLength);
            $token .= $this->alphabet[$randomKey];
        }

        return $token;
    }

    /**
     * @param int $min
     * @param int $max
     * @return int
     */
    protected function getRandomInteger($min, $max)
    {
        $range = ($max - $min);

        if ($range < 0) {
            // Not so random...
            return $min;
        }

        $log = log($range, 2);

        // Length in bytes.
        $bytes = (int) ($log / 8) + 1;

        // Length in bits.
        $bits = (int) $log + 1;

        // Set all lower bits to 1.
        $filter = (int) (1 << $bits) - 1;

        do {
            $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));

            // Discard irrelevant bits.
            $rnd = $rnd & $filter;

        } while ($rnd >= $range);

        return ($min + $rnd);
    }
}

Utilisation

<?php

use Utils\RandomStringGenerator;

// Create new instance of generator class.
$generator = new RandomStringGenerator;

// Set token length.
$tokenLength = 32;

// Call method to generate random string.
$token = $generator->generate($tokenLength);

Alphabet personnalisé

Vous pouvez utiliser un alphabet personnalisé si nécessaire. Il suffit de passer une chaîne avec les caractères pris en charge au constructeur ou au paramètre :

<?php

$customAlphabet = '0123456789ABCDEF';

// Set initial alphabet.
$generator = new RandomStringGenerator($customAlphabet);

// Change alphabet whenever needed.
$generator->setAlphabet($customAlphabet);

Voici les échantillons de sortie

SRniGU2sRQb2K1ylXKnWwZr4HrtdRgrM
q1sRUjNq1K9rG905aneFzyD5IcqD4dlC
I0euIWffrURLKCCJZ5PQFcNUCto6cQfD
AKwPJMEM5ytgJyJyGqoD5FQwxv82YvMr
duoRF6gAawNOEQRICnOUNYmStWmOpEgS
sdHUkEn4565AJoTtkc8EqJ6cC4MLEHUx
eVywMdYXczuZmHaJ50nIVQjOidEVkVna
baJGt7cdLDbIxMctLsEBWgAw5BByP5V0
iqT0B2obq3oerbeXkDVLjZrrLheW4d8f
OUQYCny6tj2TYDlTuu1KsnUyaLkeObwa

J'espère que cela pourra aider quelqu'un. A la vôtre !

0 votes

Je suis désolé mais comment je fais fonctionner ce truc ? Je l'ai fait $obj = new RandomStringGenerator; mais quelle méthode dois-je appeler ? Merci.

0 votes

@sg552, vous devriez appeler generate comme dans l'exemple ci-dessus. Il suffit de lui passer un nombre entier pour spécifier la longueur de la chaîne résultante.

0 votes

Merci, cela fonctionne pour moi mais Custom Alphabet ne l'a pas fait. J'ai eu une sortie vide quand j'ai fait écho. De toute façon, je ne suis même pas sûr de l'utilité du deuxième exemple. Custom Alphabet donc je pense que je vais rester avec le premier exemple. Merci.

40voto

Cette fonction permet de générer une clé aléatoire à l'aide de chiffres et de lettres :

function random_string($length) {
    $key = '';
    $keys = array_merge(range(0, 9), range('a', 'z'));

    for ($i = 0; $i < $length; $i++) {
        $key .= $keys[array_rand($keys)];
    }

    return $key;
}

echo random_string(50);

Exemple de sortie :

zsd16xzv3jsytnp87tk7ygv73k8zmr0ekh6ly7mxaeyeh46oe8

6 votes

Au bout d'un moment, vous arrivez aux mêmes chiffres

1 votes

Ce n'est pas totalement aléatoire car il n'y aura jamais le même chacater plus d'une fois.

8voto

  1. Générer un nombre aléatoire en utilisant votre générateur de nombres aléatoires préféré préféré
  2. Multipliez et divisez-le pour obtenir un nombre correspondant au nombre de caractères dans l'alphabet de votre code
  3. Obtenez l'élément à cet indice dans votre code alphabétique.
  4. Répétez à partir de 1) jusqu'à ce que vous ayez la longueur que vous souhaitez que vous voulez

Par exemple (en pseudo-code)

int myInt = random(0, numcharacters)
char[] codealphabet = 'ABCDEF12345'
char random = codealphabet[i]
repeat until long enough

0 votes

Théoriquement, cela ne pourrait-il pas générer la même chaîne plus d'une fois ?

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