Funkcja PHP do generowania UUID v4

233

Robiłem więc kopanie i próbowałem poskładać funkcję, która generuje prawidłowy UUID v4 w PHP. To jest najbliższy czas jaki mogłem przyjść. Moja wiedza na temat szesnastkowych, dziesiętnych, binarnych, bitowych operatorów PHP i tym podobnych prawie nie istnieje. Ta funkcja generuje prawidłowy identyfikator UUID v4 aż do jednego obszaru. UUID v4 powinien mieć postać:

xxxxxxxx-xxxx- 4 xxx- Y XXX-XXXXXXXXXXXX

gdzie y wynosi 8, 9, A lub B. To jest, gdzie funkcje zawodzą, ponieważ nie spełniają tego.

Miałem nadzieję, że ktoś z większą wiedzą niż ja w tej dziedzinie może mi pomóc i naprawić tę funkcję, aby była zgodna z tą regułą.

Funkcja jest następująca:

<?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;
}

?>

Dziękujemy wszystkim, którzy mogą mi pomóc.

anomareh
źródło
5
Jeśli korzystasz z Linuksa i jesteś trochę leniwy, możesz $newId = exec('uuidgen -r');
generetować

Odpowiedzi:

282

Na podstawie tego komentarza do instrukcji PHP możesz użyć tego:

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
źródło
43
Funkcja ta będzie tworzyć duplikaty, więc unikać go, kiedy trzeba unikalne wartości. Zauważ, że mt_rand () zawsze tworzy tę samą sekwencję liczb losowych przy tym samym nasieniu. Tak więc za każdym razem, gdy ziarno jest powtarzane, generowany jest ten sam dokładny UUID. Aby obejść ten problem, musisz go zaszczepić przy użyciu czasu i adresu mac, ale nie jestem pewien, jak to zrobiłbyś, ponieważ mt_srand () wymaga liczby całkowitej.
Pavle Predic 7.03.13
12
@PavlePredic mt_srand (crc32 (serialize ([microtime (true), 'USER_IP', 'ETC']))); (Jestem inny Wiliam: P)
Wiliam
13
Dokumenty PHP wyraźnie ostrzegają, że mt_rand () nie generuje kryptograficznie bezpiecznych wartości. Innymi słowy, wartości generowane przez tę funkcję mogą być przewidywalne. Jeśli musisz upewnić się, że identyfikatory UUID nie są przewidywalne, powinieneś raczej użyć rozwiązania Jacka poniżej, które korzysta z funkcji openssl_random_pseudo_bytes ().
Richard Keller
7
jaki jest sens generowania UUID, jeśli wypełniasz wszystkie pola śmieciami?
Eevee
1
PHP 7.0+ definiuje funkcję random_bytes (), która zawsze generuje kryptograficznie bezpieczne losowe bajty lub zgłasza wyjątek, jeśli nie jest w stanie. Jest to lepsze niż nawet openssl_random_psuedo_bytes (), którego dane wyjściowe czasami nie są kryptograficznie bezpieczne w pewnych okolicznościach.
thomasrutter
365

Zamiast podziału na poszczególne pola łatwiej jest wygenerować losowy blok danych i zmienić pozycje poszczególnych bajtów. Powinieneś także użyć lepszego generatora liczb losowych niż mt_rand ().

Zgodnie z RFC 4122 - sekcja 4.4 , musisz zmienić te pola:

  1. time_hi_and_version (bity 4-7 z 7 oktetu),
  2. clock_seq_hi_and_reserved (bit 6 i 7 z 9 oktetu)

Wszystkie pozostałe 122 bity powinny być wystarczająco losowe.

Poniższe podejście generuje 128 bitów losowych danych przy użyciu openssl_random_pseudo_bytes(), dokonuje permutacji w oktetach, a następnie używa bin2hex()i vsprintf()wykonuje ostateczne formatowanie.

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

W PHP 7 generowanie losowych sekwencji bajtów jest jeszcze prostsze przy użyciu random_bytes():

function guidv4($data = null)
{
    $data = $data ?? random_bytes(16);
    // ...
}
Jacek
źródło
9
Alternatywa dla użytkowników * nix, którzy nie mają rozszerzenia openssl:$data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16);
Iiridayn 19.04.2013
5
Ponadto ufałbym OpenSSL o wiele bardziej niż mt_rand.
Prof. Falken
3
@BrunoAugusto jest losowy i jest bardzo mało prawdopodobne (z dobrym losowym źródłem) uzyskanie duplikatów, ale dobrą praktyką jest egzekwowanie go na poziomie bazy danych.
Ja͢ck
9
Czy jest jakiś powód, aby NIE umieszczać wywołania random_bytes (16) w funkcji guidv4, a zatem nie trzeba przekazywać żadnego parametru do guidv4?
Stephen R
7
Małe ulepszenie: ustaw wartość NULL dla danych $, a następnie pierwszy wiersz funkcji jest następujący: $data = $data ?? random_bytes( 16 ); Teraz możesz określić własne losowe źródło danych lub pozwolić, aby funkcja zrobiła to za Ciebie. :-)
Stephen R
118

Każdy, kto używa zależności kompozytora , może rozważyć tę bibliotekę: https://github.com/ramsey/uuid

Nie ma nic łatwiejszego niż to:

Uuid::uuid4();
djule5
źródło
32
Och, nie wiem .... Pięć linii kodu vs. ładowanie biblioteki z zależnościami? Wolę funkcję Jacka. YMMV
Stephen R
7
+1 do Stephena. Ramsey uuid ma o wiele więcej funkcji niż tylko uuid4. Nie chcę banana !, masz całą dżunglę!
lcjury
26
UUID to nie tylko losowe ciągi. Istnieje specyfikacja tego, jak to działa. Aby wygenerować odpowiedni losowy identyfikator UUID, którego nie muszę martwić się o późniejsze odrzucenie, wolę użyć przetestowanej biblioteki niż rzucić własną implementację.
Brandon,
3
To jest UUIDv4. Jest (głównie, ale przez kilka bitów) losowy. To nie jest kryptografia. Paranoja przeciwko „kroczeniu własnym” jest głupia.
Gordon
23

w systemach uniksowych użyj jądra systemowego, aby wygenerować dla Ciebie identyfikator UUID.

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

Kredyty Samveen na https://serverfault.com/a/529319/210994

Uwaga !: Użycie tej metody w celu uzyskania identyfikatora UUID w rzeczywistości bardzo szybko wyczerpuje pulę entropii! Unikałbym używania tego w miejscu, gdzie byłoby to często nazywane.

ThorSummoner
źródło
2
Oprócz przenośności, zauważ, że losowe źródło /dev/randomblokuje się, jeśli pula entropii zostanie wyczerpana.
Ja͢ck
@Jack Czy uprzejmie proszę o dołączenie dokumentacji dotyczącej wyczerpania puli entropii w systemach unix? Byłbym zainteresowany dowiedzieć się więcej o realistycznym przypadku użycia, w którym ta metoda się psuje.
ThorSummoner,
Nie mogłem znaleźć informacji na temat tworzenia tego specjalnego źródła pliku jądra /dev/urandom, które w moim rozumieniu nie wyczerpałoby się, ale ryzykuje zwrot zduplikowanych plików UUID. Myślę, że to kompromis; czy naprawdę potrzebujesz unikalnego identyfikatora pod wpływem entropii systemu?
ThorSummoner,
13

Poszukując utworzenia identyfikatora użytkownika v4, najpierw znalazłem się na tej stronie, a potem znalazłem to na stronie 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));
}

kredyt: pavel.volyntsev

Edycja: aby wyjaśnić, ta funkcja zawsze da ci identyfikator użytkownika v4 (PHP> = 5.3.0).

Gdy funkcja com_create_guid jest dostępna (zwykle tylko w systemie Windows), użyje jej i usunie nawiasy klamrowe.

Jeśli nie jest obecny (Linux), powróci do tej silnej losowej funkcji openssl_random_pseudo_bytes, a następnie użyje vsprintf do sformatowania go w UUID v4.

Arie
źródło
5

Moja odpowiedź opiera się na komentarzu uniqid komentarza użytkownika, ale używa funkcji openssl_random_pseudo_bytes do generowania losowego ciągu zamiast czytania z/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
źródło
5

Jeśli używasz CakePHP, możesz użyć ich metody CakeText::uuid();z klasy CakeText, aby wygenerować identyfikator użytkownika RFC4122.

bisz
źródło
5

Niewielka różnica w odpowiedzi Jacka, aby dodać obsługę 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
źródło
4

Zainspirowany broofa odpowiedź jest tutaj .

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

Lub jeśli nie możesz korzystać z anonimowych funkcji.

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
źródło
1
Jeśli spojrzysz na komentarze w innych odpowiedziach, zobaczysz, że ludzie mówią, że mt_rand()nie ma gwarancji losowości.
Daniel Cheung
3

Po wyszukaniu dokładnie tej samej rzeczy i prawie zaimplementowaniu jej osobiście, pomyślałem, że warto wspomnieć, że jeśli robisz to w ramach WordPress , WP ma własną super przydatną funkcję:

$myUUID = wp_generate_uuid4();

Możesz przeczytać opis i źródło tutaj .

indextwo
źródło
1
Funkcja WP używa wyłącznie mt_rand. Więc może nie mieć wystarczająco losowości
Herbert Peters
@HerbertPeters Masz rację. Wspomniałem tylko o tym, ponieważ jest to jedna linijka. Chciałem powiedzieć, że byłoby fajnie, gdyby dodali do niego filtr, abyś mógł zwrócić bezpieczniejszą / gwarantowaną losową liczbę; ale falsedrugą
stroną
2

Co powiesz na użycie mysql do wygenerowania UUID?

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

$query = 'SELECT UUID()';
echo $conn->query($query)->fetch_row()[0];
Hoan Dang
źródło
2
UUID()Funkcja MySQL tworzy uuids v1.
staticsan
2
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4));
Cristián Carrasco
źródło
2
Dodaj wyjaśnienie do swojego kodu, aby inni mogli zrozumieć, co robi.
KFoobar
to jest to, co faktycznie wykonywana przez Symfony polyfil - github.com/symfony/polyfill-uuid/blob/master/Uuid.php#L320
Serhii Poliszczuk
1

Od Toma na 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])
aminy
źródło
3
Co jeśli nie są uruchomione w systemach Unix lub Linux / GNU? Ten kod nie działa.
Cole Johnson
4
Może to również działać bardzo wolno, jeśli / dev / random jest pusty i czeka na ponowne załadowanie entropii.
ObsidianX
1
/dev/urandompowinno być w porządku - /dev/randompowinno być używane tylko do generowania długoterminowych kluczy kryptograficznych.
Iiridayn
Na tej podstawie wymyśliłem to - wykorzystuje kilka możliwych źródeł losowości jako awarie i ucieka się do seedowania, mt_rand()jeśli nic bardziej wymyślnego nie jest dostępne.
mindplay.dk
1
Do tej pory po prostu używaj random_bytes()w PHP 7 i gotowe :-)
mindplay.dk
1

Jestem pewien, że istnieje bardziej elegancki sposób na konwersję z binarnej na dziesiętną dla części 4xxxi yxxx. Ale jeśli chcesz używać openssl_random_pseudo_bytesswojego kryptograficznie bezpiecznego generatora liczb, używam tego:

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))
    );
Barakus
źródło