Funzione PHP per generare UUID v4


233

Quindi ho fatto qualche ricerca e ho provato a mettere insieme una funzione che genera un UUID v4 valido in PHP. Questo è il più vicino che sono stato in grado di venire. La mia conoscenza in esadecimali, decimali, binari, operatori bit per bit di PHP e simili è quasi inesistente. Questa funzione genera un UUID v4 valido fino a un'area. Un UUID v4 dovrebbe essere nella forma di:

xxxxxxxx-xxxx- 4 xxx- y xxx-xxxxxxxxxxxx

dove y è 8, 9, A o B. Qui è dove le funzioni falliscono perché non aderiscono a ciò.

Speravo che qualcuno con più conoscenza di me in quest'area potesse darmi una mano e aiutarmi a sistemare questa funzione in modo che aderisca a quella regola.

La funzione è la seguente:

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

?>

Grazie a chiunque mi possa aiutare.


5
Se sei su Linux e se sei un po 'pigro puoi generarli con$newId = exec('uuidgen -r');
JorgeGarza,

Risposte:


282

Tratto da questo commento sul manuale di PHP, puoi usare questo:

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

43
Questa funzione sarà creare duplicati, in modo da evitare quando si ha bisogno valori univoci. Si noti che mt_rand () produrrà sempre la stessa sequenza di numeri casuali dato lo stesso seme. Quindi ogni volta che un seme viene ripetuto, viene generato lo stesso UUID esatto. Per ovviare a questo, dovresti eseguirne il seeding usando time e mac address, ma non sono sicuro di come lo faresti, poiché mt_srand () richiede un numero intero.
Pavle Predic

12
@PavlePredic mt_srand (crc32 (serializza ([microtime (true), 'USER_IP', 'ETC']))); (sono un altro wiliam: P)
Wiliam

13
I documenti PHP avvertono esplicitamente che mt_rand () non genera valori crittograficamente sicuri. In altre parole, i valori generati da questa funzione possono essere prevedibili. Se devi assicurarti che gli UUID non siano prevedibili, dovresti piuttosto usare la soluzione di Jack di seguito, che fa uso della funzione openssl_random_pseudo_bytes ().
Richard Keller,

7
che diamine serve a generare un UUID se riempi tutti i campi di immondizia?
Eevee,

1
PHP 7.0+ definisce la funzione random_bytes () che genererà sempre byte casuali crittograficamente sicuri o genererà un'eccezione se non è possibile. Questo è meglio persino di openssl_random_psuedo_bytes () il cui output a volte non è sicuro crittograficamente in alcune circostanze.
thomasrutter,

365

Invece di scomporlo in singoli campi, è più semplice generare un blocco casuale di dati e modificare le posizioni dei singoli byte. Dovresti anche usare un generatore di numeri casuali migliore di mt_rand ().

Secondo RFC 4122 - Sezione 4.4 , è necessario modificare questi campi:

  1. time_hi_and_version (bit 4-7 del 7 ° ottetto),
  2. clock_seq_hi_and_reserved (bit 6 e 7 del 9 ° ottetto)

Tutti gli altri 122 bit dovrebbero essere sufficientemente casuali.

L'approccio seguente genera 128 bit di dati casuali utilizzando openssl_random_pseudo_bytes(), effettua le permutazioni sugli ottetti e quindi utilizza bin2hex()e vsprintf()per eseguire la formattazione finale.

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

Con PHP 7, generare sequenze di byte casuali è ancora più semplice usando random_bytes():

function guidv4($data = null)
{
    $data = $data ?? random_bytes(16);
    // ...
}

9
Un'alternativa per gli utenti * nix che non hanno l'estensione openssl:$data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16);
Iiridayn

5
Inoltre, mi affiderei a OpenSSL molto più di mt_rand.
Prof. Falken,

3
@BrunoAugusto è casuale ed è estremamente improbabile (con una buona fonte casuale) ottenere duplicati, ma è una buona pratica applicarlo a livello di database.
Ja͢ck,

9
C'è qualche motivo per NON inserire la chiamata random_bytes (16) all'interno della funzione guidv4 e quindi non è necessario passare alcun parametro a guidv4?
Stephen R

7
Piccolo miglioramento: imposta un valore NULL predefinito per $ data, quindi la prima riga della funzione è questa: $data = $data ?? random_bytes( 16 ); ora PUOI specificare la tua origine dati casuale o lasciare che la funzione lo faccia per te. :-)
Stephen R,

118

Chiunque utilizzi le dipendenze del compositore , potresti prendere in considerazione questa libreria: https://github.com/ramsey/uuid

Non è più facile di così:

Uuid::uuid4();

32
Oh, non lo so .... Cinque righe di codice rispetto al caricamento di una libreria con dipendenze? Preferisco la funzione di Jack. YMMV
Stephen R,

7
+1 a Stefano. Ramsey uuid ha molte più funzionalità di uuid4. Non voglio una banana! Ecco qui tutta la giungla!
lcjury

26
Gli UUID non sono solo stringhe casuali. C'è una specifica su come funziona. Per generare un UUID casuale appropriato che non devo preoccuparmi di essere rifiutato in seguito, preferirei utilizzare una libreria testata piuttosto che implementare la mia implementazione.
Brandon,

3
È un UUIDv4. È (principalmente, ma per alcuni bit) casuale. Questa non è crittografia. La paranoia contro il "far rotolare il tuo" è sciocca.
Gordon,

23

sui sistemi unix, usa il kernel di sistema per generare un uuid per te.

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

Accredita Samveen su https://serverfault.com/a/529319/210994

Nota !: L'uso di questo metodo per ottenere un uuido esaurisce infatti il ​​pool di entropia, molto rapidamente! Eviterei di usarlo dove verrebbe chiamato frequentemente.


2
Oltre alla portabilità, nota che la fonte casuale è /dev/randomche si blocca se il pool di entropia è esaurito.
Ja͢ck,

@Jack Collegheresti gentilmente alcuni documenti sull'argomento dell'esaurimento del pool entropico su sistemi unix, per favore? Sarei interessato a saperne di più su un caso d'uso realistico in cui questo metodo non funziona.
ThorSummoner,

Non sono stato in grado di trovare informazioni sulla creazione di questo speciale file del kernel da /dev/urandomcui, a mio avviso, non si esaurirebbe, ma rischia di restituire uuidi duplicati. Immagino sia un compromesso; hai davvero bisogno di un ID unico influenzato dall'entropia del sistema?
ThorSummoner,

13

Nella mia ricerca per creare un uuid v4, sono arrivato prima a questa pagina, poi l'ho trovato su 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));
}

credito: pavel.volyntsev

Modifica: per chiarire, questa funzione ti darà sempre un uuid v4 (PHP> = 5.3.0).

Quando la funzione com_create_guid è disponibile (di solito solo su Windows), la utilizzerà e rimuoverà le parentesi graffe.

Se non presente (Linux), ricadrà su questa forte funzione random openssl_random_pseudo_bytes, quindi utilizzerà vsprintf per formattarlo in v4 uuid.


5

La mia risposta si basa sul commento dell'utente uniqid ma utilizza la funzione openssl_random_pseudo_bytes per generare una stringa casuale invece di leggere da/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

5

Se lo usi CakePHPpuoi usare il loro metodo CakeText::uuid();dalla classe CakeText per generare un uuid RFC4122.


5

Una leggera variazione sulla risposta di Jack per aggiungere supporto a 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));
}

4

Ispirato da broofa risposta s' qui .

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

O se non è possibile utilizzare funzioni anonime.

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

1
Se guardi i commenti in altre risposte, vedresti che la gente dice che mt_rand()non è garantita la casualità.
Daniel Cheung,

3

Avendo cercato esattamente la stessa cosa e quasi implementato una versione di questo, ho pensato che valesse la pena ricordare che, se lo stai facendo in un framework WordPress , WP ha la sua funzione super-utile proprio per questo:

$myUUID = wp_generate_uuid4();

Puoi leggere la descrizione e la fonte qui .


1
La funzione WP utilizza esclusivamente mt_rand. Quindi potrebbe non avere abbastanza casualità
Herbert Peters,

@HerbertPeters Hai ragione. L'ho menzionato solo perché è una linea. Stavo per dire che sarebbe stato pulito se avessero aggiunto un filtro per poter restituire un numero più sicuro / garantito; ma il falserovescio della
medaglia

2

Che ne dici di usare mysql per generare l'UUID per te?

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

$query = 'SELECT UUID()';
echo $conn->query($query)->fetch_row()[0];

2
La UUID()funzione di MySQL crea uuidi v1.
staticsan


1

Da tom, su 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])

3
E se non eseguono Unix o Linux / GNU? Questo codice non funzionerà.
Cole Johnson,

4
Questo ha anche il potenziale di funzionare molto lentamente se / dev / random è vuoto e è in attesa di ricaricare più entropia.
ObsidianX,

1
/dev/urandomdovrebbe andare bene - /dev/randomdovrebbe essere usato solo per la generazione di chiavi crittografiche a lungo termine.
Iiridayn

Sulla base di ciò, mi sono inventato questo : utilizza diverse possibili fonti di casualità come fallback e ricorre al seeding mt_rand()se non è disponibile nulla di più elaborato.
mindplay.dk,

1
Ormai, basta usare random_bytes()in PHP 7 e il gioco è fatto :-)
mindplay.dk,

1

Sono sicuro che esiste un modo più elegante per eseguire la conversione da binario a decimale per le porzioni 4xxxe yxxx. Ma se vuoi usare openssl_random_pseudo_bytescome generatore di numeri crittograficamente sicuro, questo è quello che uso:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.