Fonction PHP pour générer l'UUID v4

233

J'ai donc fait quelques recherches et j'ai essayé de reconstituer une fonction qui génère un UUID v4 valide en PHP. C'est le plus proche que j'ai pu venir. Mes connaissances en hexadécimal, décimal, binaire, opérateurs bit à bit de PHP et similaires sont presque inexistantes. Cette fonction génère un UUID v4 valide jusqu'à une zone. Un UUID v4 doit se présenter sous la forme de:

xxxxxxxx-xxxx- 4 xxx- y xxx-xxxxxxxxxxxx

y est 8, 9, A ou B. C'est là que les fonctions échouent car elles n'y adhèrent pas.

J'espérais que quelqu'un avec plus de connaissances que moi dans ce domaine pourrait me donner un coup de main et m'aider à corriger cette fonction pour qu'elle respecte cette règle.

La fonction est la suivante:

<?php

function gen_uuid() {
 $uuid = array(
  'time_low'  => 0,
  'time_mid'  => 0,
  'time_hi'  => 0,
  'clock_seq_hi' => 0,
  'clock_seq_low' => 0,
  'node'   => array()
 );

 $uuid['time_low'] = mt_rand(0, 0xffff) + (mt_rand(0, 0xffff) << 16);
 $uuid['time_mid'] = mt_rand(0, 0xffff);
 $uuid['time_hi'] = (4 << 12) | (mt_rand(0, 0x1000));
 $uuid['clock_seq_hi'] = (1 << 7) | (mt_rand(0, 128));
 $uuid['clock_seq_low'] = mt_rand(0, 255);

 for ($i = 0; $i < 6; $i++) {
  $uuid['node'][$i] = mt_rand(0, 255);
 }

 $uuid = sprintf('%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x',
  $uuid['time_low'],
  $uuid['time_mid'],
  $uuid['time_hi'],
  $uuid['clock_seq_hi'],
  $uuid['clock_seq_low'],
  $uuid['node'][0],
  $uuid['node'][1],
  $uuid['node'][2],
  $uuid['node'][3],
  $uuid['node'][4],
  $uuid['node'][5]
 );

 return $uuid;
}

?>

Merci à tous ceux qui peuvent m'aider.

anomareh
la source
5
Si vous êtes sous Linux et si vous êtes un peu paresseux, vous pouvez les générer avec$newId = exec('uuidgen -r');
JorgeGarza

Réponses:

282

Tiré de ce commentaire sur le manuel PHP, vous pouvez utiliser ceci:

function gen_uuid() {
    return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
        // 32 bits for "time_low"
        mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),

        // 16 bits for "time_mid"
        mt_rand( 0, 0xffff ),

        // 16 bits for "time_hi_and_version",
        // four most significant bits holds version number 4
        mt_rand( 0, 0x0fff ) | 0x4000,

        // 16 bits, 8 bits for "clk_seq_hi_res",
        // 8 bits for "clk_seq_low",
        // two most significant bits holds zero and one for variant DCE1.1
        mt_rand( 0, 0x3fff ) | 0x8000,

        // 48 bits for "node"
        mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
    );
}
William
la source
43
Cette fonction va créer des doublons, afin d' éviter quand vous avez besoin des valeurs uniques. Notez que mt_rand () produira toujours la même séquence de nombres aléatoires étant donné la même graine. Ainsi, chaque fois qu'une graine est répétée, le même UUID exact est généré. Pour contourner ce problème, vous devez l'amorcer en utilisant l'heure et l'adresse mac, mais je ne sais pas comment vous le feriez, car mt_srand () nécessite un entier.
Pavle Predic
12
@PavlePredic mt_srand (crc32 (sérialiser ([microtime (vrai), 'USER_IP', 'ETC'])))); (je suis un autre wiliam: P)
Wiliam
13
Les documents PHP préviennent explicitement que mt_rand () ne génère pas de valeurs sécurisées cryptographiquement. En d'autres termes, les valeurs générées par cette fonction peuvent être prévisibles. Si vous devez vous assurer que les UUID ne sont pas prévisibles, vous devriez plutôt utiliser la solution de Jack ci-dessous, qui utilise la fonction openssl_random_pseudo_bytes ().
Richard Keller
7
à quoi bon générer un UUID si vous remplissez chaque champ avec des ordures?
Évoli
1
PHP 7.0+ définit la fonction random_bytes () qui générera toujours des octets aléatoires cryptographiquement sécurisés ou lèvera une exception s'il ne le peut pas. C'est mieux que même openssl_random_psuedo_bytes () dont la sortie n'est parfois pas sécurisée cryptographiquement dans certaines circonstances.
thomasrutter
365

Au lieu de le décomposer en champs individuels, il est plus facile de générer un bloc de données aléatoire et de modifier les positions d'octets individuelles. Vous devriez également utiliser un meilleur générateur de nombres aléatoires que mt_rand ().

Selon RFC 4122 - Section 4.4 , vous devez modifier ces champs:

  1. time_hi_and_version (bits 4-7 du 7e octet),
  2. clock_seq_hi_and_reserved (bits 6 et 7 du 9e octet)

Tous les 122 autres bits doivent être suffisamment aléatoires.

L'approche suivante génère 128 bits de données aléatoires en utilisant openssl_random_pseudo_bytes(), effectue les permutations sur les octets, puis utilise bin2hex()et vsprintf()pour effectuer le formatage final.

function guidv4($data)
{
    assert(strlen($data) == 16);

    $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
    $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10

    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

echo guidv4(openssl_random_pseudo_bytes(16));

Avec PHP 7, la génération de séquences d'octets aléatoires est encore plus simple en utilisant random_bytes():

function guidv4($data = null)
{
    $data = $data ?? random_bytes(16);
    // ...
}
Jack
la source
9
Une alternative pour les utilisateurs de * nix qui n'ont pas l'extension openssl:$data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16);
Iiridayn
5
De plus, je ferais beaucoup plus confiance à OpenSSL qu'à mt_rand.
Prof.Falken
3
@BrunoAugusto c'est aléatoire, et il est extrêmement peu probable (avec une bonne source aléatoire) d'obtenir des doublons, mais c'est une bonne pratique de l'appliquer au niveau de la base de données.
Ja͢ck
9
Y a-t-il une raison de NE PAS placer l'appel random_bytes (16) à l'intérieur de la fonction guidv4 et donc de ne pas devoir passer de paramètre à guidv4?
Stephen R
7
Petite amélioration: définissez une valeur NULL par défaut pour $ data, puis la première ligne de la fonction est la suivante: $data = $data ?? random_bytes( 16 ); vous pouvez maintenant spécifier votre propre source de données aléatoire ou laisser la fonction le faire pour vous. :-)
Stephen R
118

Quiconque utilise des dépendances de compositeur , vous voudrez peut-être envisager cette bibliothèque: https://github.com/ramsey/uuid

Ce n'est pas plus simple que ça:

Uuid::uuid4();
djule5
la source
32
Oh, je ne sais pas ... Cinq lignes de code contre le chargement d'une bibliothèque avec des dépendances? Je préfère la fonction de Jack. YMMV
Stephen R
7
+1 à Stephen. Ramsey uuid a beaucoup plus de fonctionnalités que juste uuid4. Je ne veux pas une banane!, Vous avez ici toute la jungle!
lcjury
26
Les UUID ne sont pas seulement des chaînes aléatoires. Il y a une spécification sur son fonctionnement. Pour générer un UUID aléatoire approprié que je n'ai pas à craindre d'être rejeté plus tard, je préfère utiliser une bibliothèque testée que rouler ma propre implémentation.
Brandon
3
C'est un UUIDv4. C'est (surtout, mais pour quelques bits) aléatoire. Ce n'est pas de la cryptographie. La paranoïa contre «rouler soi-même» est idiote.
Gordon
23

sur les systèmes unix, utilisez le noyau du système pour générer un uuid pour vous.

file_get_contents('/proc/sys/kernel/random/uuid')

Credit Samveen sur https://serverfault.com/a/529319/210994

Remarque!: L'utilisation de cette méthode pour obtenir un uuid épuise en fait le pool d'entropie, très rapidement! J'éviterais d'utiliser ceci où il serait appelé fréquemment.

ThorSummoner
la source
2
Outre la portabilité, notez que la source aléatoire est celle /dev/randomqui bloque si le pool d'entropie est épuisé.
Ja͢ck
@Jack Pourriez-vous lier avec bonté de la documentation sur le sujet de l'épuisement du pool d'entropie sur les systèmes Unix, s'il vous plaît? Je serais intéressé d'en savoir plus sur un cas d'utilisation réaliste où cette méthode tombe en panne.
ThorSummoner
Je n'ai pas été en mesure de trouver des informations sur la création de cette source de fichier noyau spéciale /dev/urandom, qui, à ma connaissance, ne s'épuiserait pas, mais risque de renvoyer des uuids en double. Je suppose que c'est un compromis; avez-vous vraiment besoin d'un identifiant unique influencé par l'entropie du système?
ThorSummoner
13

Dans ma recherche d'une création d'un uuid v4, je suis d'abord arrivé sur cette page, puis j'ai trouvé ceci sur http://php.net/manual/en/function.com-create-guid.php

function guidv4()
{
    if (function_exists('com_create_guid') === true)
        return trim(com_create_guid(), '{}');

    $data = openssl_random_pseudo_bytes(16);
    $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
    $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

crédit: pavel.volyntsev

Edit: pour clarifier, cette fonction vous donnera toujours un uuid v4 (PHP> = 5.3.0).

Lorsque la fonction com_create_guid est disponible (généralement uniquement sous Windows), elle l'utilisera et supprimera les accolades.

S'il n'est pas présent (Linux), il se rabattra sur cette fonction opensl_random_pseudo_bytes aléatoire forte, il utilisera ensuite vsprintf pour le formater en uuid v4.

Arie
la source
5

Ma réponse est basée sur le commentaire d' un utilisateur uniqid mais utilise la fonction openssl_random_pseudo_bytes pour générer une chaîne aléatoire au lieu de lire à partir de/dev/urandom

function guid()
{
    $randomString = openssl_random_pseudo_bytes(16);
    $time_low = bin2hex(substr($randomString, 0, 4));
    $time_mid = bin2hex(substr($randomString, 4, 2));
    $time_hi_and_version = bin2hex(substr($randomString, 6, 2));
    $clock_seq_hi_and_reserved = bin2hex(substr($randomString, 8, 2));
    $node = bin2hex(substr($randomString, 10, 6));

    /**
     * Set the four most significant bits (bits 12 through 15) of the
     * time_hi_and_version field to the 4-bit version number from
     * Section 4.1.3.
     * @see http://tools.ietf.org/html/rfc4122#section-4.1.3
    */
    $time_hi_and_version = hexdec($time_hi_and_version);
    $time_hi_and_version = $time_hi_and_version >> 4;
    $time_hi_and_version = $time_hi_and_version | 0x4000;

    /**
     * Set the two most significant bits (bits 6 and 7) of the
     * clock_seq_hi_and_reserved to zero and one, respectively.
     */
    $clock_seq_hi_and_reserved = hexdec($clock_seq_hi_and_reserved);
    $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved >> 2;
    $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved | 0x8000;

    return sprintf('%08s-%04s-%04x-%04x-%012s', $time_low, $time_mid, $time_hi_and_version, $clock_seq_hi_and_reserved, $node);
} // guid
Victor Smirnov
la source
5

Si vous utilisez, CakePHPvous pouvez utiliser leur méthode CakeText::uuid();de la classe CakeText pour générer un uuid RFC4122.

bish
la source
5

Une légère variation sur la réponse de Jack pour ajouter le support de PHP <7:

// Get an RFC-4122 compliant globaly unique identifier
function get_guid() {
    $data = PHP_MAJOR_VERSION < 7 ? openssl_random_pseudo_bytes(16) : random_bytes(16);
    $data[6] = chr(ord($data[6]) & 0x0f | 0x40);    // Set version to 0100
    $data[8] = chr(ord($data[8]) & 0x3f | 0x80);    // Set bits 6-7 to 10
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
Danny Beckett
la source
4

Inspiré par broofa réponse de .

preg_replace_callback('/[xy]/', function ($matches)
{
  return dechex('x' == $matches[0] ? mt_rand(0, 15) : (mt_rand(0, 15) & 0x3 | 0x8));
}
, 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx');

Ou en cas d'impossibilité d'utiliser des fonctions anonymes.

preg_replace_callback('/[xy]/', create_function(
  '$matches',
  'return dechex("x" == $matches[0] ? mt_rand(0, 15) : (mt_rand(0, 15) & 0x3 | 0x8));'
)
, 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx');
MichaelRushton
la source
1
Si vous regardez les commentaires dans d'autres réponses, vous verrez des gens dire mt_rand()que le caractère aléatoire n'est pas garanti.
Daniel Cheung
3

Après avoir recherché exactement la même chose et presque implémenté une version de cela moi-même, j'ai pensé qu'il valait la peine de mentionner que, si vous le faites dans un cadre WordPress , WP a sa propre fonction très pratique pour exactement cela:

$myUUID = wp_generate_uuid4();

Vous pouvez lire la description et la source ici .

indextwo
la source
1
La fonction WP utilise exclusivement mt_rand. Donc peut-être pas assez aléatoire
Herbert Peters
@HerbertPeters Vous avez raison. Je ne l'ai mentionné que parce qu'il s'agit d'un vol simple. J'allais dire que ce serait bien s'ils avaient ajouté un filtre pour que vous puissiez retourner un nombre aléatoire plus sécurisé / garanti; mais le revers de la médaille est que, si vous étiez si enclin, vous pourriez également revenir false🤷
indextwo
2

Que diriez-vous d'utiliser mysql pour générer l'uuid pour vous?

$conn = new mysqli($servername, $username, $password, $dbname, $port);

$query = 'SELECT UUID()';
echo $conn->query($query)->fetch_row()[0];
Hoan Dang
la source
2
La UUID()fonction de MySQL crée des uuids v1.
staticsan
2
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4));
Cristián Carrasco
la source
2
Veuillez ajouter une explication à votre code pour aider les autres à comprendre ce qu'il fait.
KFoobar
c'est ce que fait réellement Symfony polyfil - github.com/symfony/polyfill-uuid/blob/master/Uuid.php#L320
Serhii Polishchuk
1

De tom, sur http://www.php.net/manual/en/function.uniqid.php

$r = unpack('v*', fread(fopen('/dev/random', 'r'),16));
$uuid = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
    $r[1], $r[2], $r[3], $r[4] & 0x0fff | 0x4000,
    $r[5] & 0x3fff | 0x8000, $r[6], $r[7], $r[8])
amgine
la source
3
Et s'ils n'exécutent pas Unix ou Linux / GNU? Ce code ne fonctionnera pas.
Cole Johnson
4
Cela peut également fonctionner très lentement si / dev / random est vide et attend plus d'entropie pour se recharger.
ObsidianX
1
/dev/urandomdevrait être bien - /dev/randomne devrait être utilisé que pour la génération de clés cryptographiques à long terme.
Iiridayn
Sur cette base, j'ai trouvé cela - il utilise plusieurs sources possibles d'aléatoire comme solutions de rechange et recourt à l'ensemencement mt_rand()si rien de plus sophistiqué n'est disponible.
mindplay.dk
1
À présent, utilisez simplement random_bytes()en PHP 7 et c'est parti :-)
mindplay.dk
1

Je suis sûr qu'il existe une façon plus élégante de faire la conversion de binaire en décimal pour les portions 4xxxet yxxx. Mais si vous souhaitez l'utiliser openssl_random_pseudo_bytescomme générateur de nombres sécurisé sur le plan cryptographique, voici ce que j'utilise:

return sprintf('%s-%s-%04x-%04x-%s',
    bin2hex(openssl_random_pseudo_bytes(4)),
    bin2hex(openssl_random_pseudo_bytes(2)),
    hexdec(bin2hex(openssl_random_pseudo_bytes(2))) & 0x0fff | 0x4000,
    hexdec(bin2hex(openssl_random_pseudo_bytes(2))) & 0x3fff | 0x8000,
    bin2hex(openssl_random_pseudo_bytes(6))
    );
Baracus
la source