Función PHP para generar v4 UUID

233

Así que he estado investigando un poco y he estado tratando de reconstruir una función que genere un UUID v4 válido en PHP. Esto es lo más cerca que he podido llegar. Mi conocimiento en hexadecimal, decimal, binario, operadores bit a bit de PHP y similares es casi inexistente. Esta función genera un UUID v4 válido hasta un área. Un v4 UUID debe tener la forma de:

xxxxxxxx-xxxx- 4 xxx- y xxx-xxxxxxxxxxxx

donde y es 8, 9, A o B. Aquí es donde las funciones fallan, ya que no se adhiere a eso.

Esperaba que alguien con más conocimiento que yo en esta área pudiera echarme una mano y ayudarme a arreglar esta función para que se adhiera a esa regla.

La función es la siguiente:

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

?>

Gracias a cualquiera que pueda ayudarme.

anomareh
fuente
55
Si estás en Linux y eres un poco vago, puedes generarlos con$newId = exec('uuidgen -r');
JorgeGarza

Respuestas:

282

Tomado de este comentario en el manual de PHP, podría usar esto:

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 )
    );
}
Guillermo
fuente
43
Esta función será crear duplicados, lo que evitar cuando se necesita valores únicos. Tenga en cuenta que mt_rand () siempre producirá la misma secuencia de números aleatorios dada la misma semilla. Entonces, cada vez que se repite una semilla, se genera el mismo UUID exacto. Para evitar esto, necesitaría sembrarlo usando el tiempo y la dirección mac, pero no estoy seguro de cómo hacerlo, ya que mt_srand () requiere un número entero.
Pavle Predic
12
@PavlePredic mt_srand (crc32 (serialize ([microtime (true), 'USER_IP', 'ETC']))); (Soy otro wiliam: P)
Wiliam
13
Los documentos PHP advierten explícitamente que mt_rand () no genera valores criptográficamente seguros. En otras palabras, los valores generados por esta función pueden ser predecibles. Si necesita asegurarse de que los UUID no sean predecibles, debería usar la solución de Jack a continuación, que utiliza la función openssl_random_pseudo_bytes ().
Richard Keller
77
¿Cuál es el punto de generar un UUID si llena cada campo con basura?
Eevee
1
PHP 7.0+ define la función random_bytes () que siempre generará bytes aleatorios criptográficamente seguros o lanzará una excepción si no puede hacerlo. Esto es mejor que incluso openssl_random_psuedo_bytes () cuya salida a veces no es criptográficamente segura en algunas circunstancias.
thomasrutter
365

En lugar de dividirlo en campos individuales, es más fácil generar un bloque aleatorio de datos y cambiar las posiciones de los bytes individuales. También debe usar un generador de números aleatorios mejor que mt_rand ().

De acuerdo con RFC 4122 - Sección 4.4 , debe cambiar estos campos:

  1. time_hi_and_version (bits 4-7 del séptimo octeto),
  2. clock_seq_hi_and_reserved (bit 6 y 7 del noveno octeto)

Todos los otros 122 bits deben ser suficientemente aleatorios.

El siguiente enfoque genera 128 bits de datos aleatorios utilizando openssl_random_pseudo_bytes(), realiza las permutaciones en los octetos y luego utiliza bin2hex()y realiza vsprintf()el formateo 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));

Con PHP 7, generar secuencias de bytes al azar es aún más simple usando random_bytes():

function guidv4($data = null)
{
    $data = $data ?? random_bytes(16);
    // ...
}
Jack
fuente
99
Una alternativa para los usuarios de * nix que no tienen la extensión openssl:$data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16);
Iiridayn
55
Además, confiaría en OpenSSL mucho más que mt_rand.
Prof. Falken
3
@BrunoAugusto es aleatorio y es extremadamente improbable (con una buena fuente aleatoria) obtener duplicados, pero es una buena práctica aplicarlo a nivel de base de datos.
Ja͢ck
99
¿Hay alguna razón para NO poner la llamada random_bytes (16) dentro de la función guidv4 y, por lo tanto, no tener que pasar ningún parámetro a guidv4?
Stephen R
77
Pequeña mejora: establezca un valor predeterminado NULL para $ data, y luego la primera línea de la función es esta: $data = $data ?? random_bytes( 16 ); ahora PUEDE especificar su propia fuente de datos aleatoria, o dejar que la función lo haga por usted. :-)
Stephen R
118

Cualquiera que use dependencias del compositor , puede considerar esta biblioteca: https://github.com/ramsey/uuid

No hay nada más fácil que esto:

Uuid::uuid4();
djule5
fuente
32
Oh, no sé ... ¿Cinco líneas de código frente a cargar una biblioteca con dependencias? Prefiero la función de Jack. YMMV
Stephen R
77
+1 a Stephen. Ramsey uuid tiene mucha más funcionalidad que solo uuid4. ¡Quiero un plátano !, ¡aquí tienes toda la jungla!
lcjury
26
Los UUID no son solo cadenas aleatorias. Hay una especificación de cómo funciona. Para generar un UUID aleatorio adecuado del que no tenga que preocuparme de que me rechacen más tarde, prefiero usar una biblioteca probada que lanzar mi propia implementación.
Brandon
3
Es un UUIDv4. Es (principalmente, pero por unos pocos bits) al azar. Esto no es criptografía. La paranoia contra "rodar el tuyo" es una tontería.
Gordon
23

en sistemas unix, use el núcleo del sistema para generar un uuid para usted.

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

Crédito Samveen en https://serverfault.com/a/529319/210994

¡Nota !: ¡El uso de este método para obtener una uuida de hecho agota el grupo de entropía, muy rápidamente! Evitaría usar esto donde se llamaría con frecuencia.

ThorSummoner
fuente
2
Además de la portabilidad, tenga en cuenta que la fuente aleatoria es la /dev/randomque bloquea si se agota el grupo de entropía.
Ja͢ck
@Jack ¿Podría vincular amablemente alguna documentación sobre el tema del agotamiento del grupo de entropía en sistemas Unix, por favor? Me interesaría saber más sobre un caso de uso realista donde este método se descompone.
ThorSummoner
No pude encontrar información sobre /dev/urandomcómo hacer este origen de archivo de núcleo especial , que a mi entender no se agotaría, pero corre el riesgo de devolver uuids duplicados. Supongo que es una compensación; ¿Realmente necesitas una identificación única influenciada por la entropía del sistema?
ThorSummoner
13

En mi búsqueda de crear un v4 uuid, llegué primero a esta página, luego encontré esto en 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édito: pavel.volyntsev

Editar: para aclarar, esta función siempre le dará un uuid v4 (PHP> = 5.3.0).

Cuando la función com_create_guid está disponible (generalmente solo en Windows), la usará y eliminará las llaves.

Si no está presente (Linux), recurrirá a esta función fuerte openssl_random_pseudo_bytes aleatoria, luego utilizará vsprintf para formatearla en v4 uuid.

Arie
fuente
5

Mi respuesta se basa en un comentario de usuario uniqid pero usa la función openssl_random_pseudo_bytes para generar cadenas aleatorias en lugar de leer/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
fuente
5

Si lo usa CakePHP, puede usar su método CakeText::uuid();de la clase CakeText para generar un uuid RFC4122.

Bish
fuente
5

Una ligera variación en la respuesta de Jack para agregar soporte para 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
fuente
4

Inspirado por la respuesta de broofa aquí .

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 si no puede utilizar funciones anónimas.

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
fuente
1
Si observa los comentarios en otras respuestas, verá que la gente dice mt_rand()que no se garantiza el azar.
Daniel Cheung
3

Después de buscar exactamente lo mismo y casi implementar una versión de esto yo mismo, pensé que valía la pena mencionar que, si lo estás haciendo dentro de un marco de WordPress , WP tiene su propia función súper práctica para exactamente esto:

$myUUID = wp_generate_uuid4();

Puedes leer la descripción y la fuente aquí .

indextwo
fuente
1
La función WP usa mt_rand exclusivamente. Por lo tanto, podría no tener suficiente aleatoriedad
Herbert Peters
@HerbertPeters Tienes razón. Solo lo mencioné porque es de una sola línea. Iba a decir que sería bueno si le hubieran agregado un filtro para que pudiera devolver un número aleatorio más seguro / garantizado; pero la otra cara de eso es que, si estuvieras tan inclinado, también podrías regresar false🤷
indextwo
2

¿Qué tal si usas mysql para generar el uuid por ti?

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

$query = 'SELECT UUID()';
echo $conn->query($query)->fetch_row()[0];
Hoan Dang
fuente
2
La UUID()función de MySQL crea v1 uuids.
staticsan
2
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4));
Cristián Carrasco
fuente
2
Agregue una explicación a su código para ayudar a otros a comprender lo que hace.
KFoobar
esto es lo que realmente hizo Symfony polyfil - github.com/symfony/polyfill-uuid/blob/master/Uuid.php#L320
Serhii Polishchuk
1

De tom, en 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
fuente
3
¿Qué pasa si no están ejecutando Unix o Linux / GNU? Este código no funcionará.
Cole Johnson el
44
Esto también tiene el potencial de ejecutarse muy lentamente si / dev / random está vacío y está esperando que se vuelva a cargar más entropía.
ObsidianX
1
/dev/urandomdebe estar bien, /dev/randomsolo debe usarse para generar claves criptográficas a largo plazo.
Iiridayn
En base a eso, se me ocurrió esto : utiliza varias fuentes posibles de aleatoriedad como alternativas, y recurre a la siembra mt_rand()si no hay nada más elegante disponible.
mindplay.dk
1
Por ahora, solo utilícelo random_bytes()en PHP 7 y listo :-)
mindplay.dk
1

Estoy seguro de que hay una forma más elegante de hacer la conversión de binario a decimal para las porciones 4xxxy yxxx. Pero si quieres usarlo openssl_random_pseudo_bytescomo tu generador de números criptográficamente seguro, esto es lo que yo 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))
    );
Baracus
fuente