Função PHP para gerar UUID v4

233

Então, eu tenho pesquisado bastante e tenho tentado reunir uma função que gera um UUID v4 válido em PHP. Este é o mais próximo que pude chegar. Meu conhecimento em operadores hexadecimais, decimais, binários, bit a bit do PHP e similares é quase inexistente. Esta função gera um UUID v4 válido até uma área. Um UUID v4 deve estar na forma de:

xxxxxxxx-xxxx- 4 xxx- y xxx-xxxxxxxxxxxxxx

onde y é 8, 9, A ou B. É aqui que as funções falham, pois não aderem a isso.

Eu esperava que alguém com mais conhecimento do que eu nesta área pudesse me ajudar e consertar essa função para que ela cumprisse essa regra.

A função é a seguinte:

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

?>

Agradeço a qualquer um que possa me ajudar.

anomareh
fonte
5
Se você estiver em Linux e se você é um pouco preguiçoso você pode generete-los com$newId = exec('uuidgen -r');
JorgeGarza

Respostas:

282

Retirado deste comentário no manual do PHP, você poderia usar o seguinte:

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
fonte
43
Esta função irá criar duplicatas, para evitá-lo quando você precisa de valores exclusivos. Observe que mt_rand () sempre produzirá a mesma sequência de números aleatórios, dada a mesma semente. Portanto, toda vez que uma semente é repetida, o mesmo UUID exato é gerado. Para contornar isso, você precisaria propagá-lo usando o horário e o endereço mac, mas não sei como você faria isso, já que mt_srand () requer um número inteiro.
Pavle Predic
12
@PavlePredic mt_srand (crc32 (serialize ([microtime (true), 'USER_IP', 'ETC']))); (eu sou outra wiliam: P)
Wiliam
13
Os documentos do PHP alertam explicitamente que mt_rand () não gera valores criptograficamente seguros. Em outras palavras, os valores gerados por esta função podem ser previsíveis. Se você precisar garantir que os UUIDs não sejam previsíveis, use a solução de Jack abaixo, que utiliza a função openssl_random_pseudo_bytes ().
Richard Keller
7
o que diabos é o ponto de gerar um UUID se você preencher todos os campos com lixo?
Eevee
1
O PHP 7.0+ define a função random_bytes () que sempre gera bytes aleatórios criptograficamente seguros ou gera uma exceção, se não for possível. Isso é melhor do que o openssl_random_psuedo_bytes (), cuja saída às vezes não é criptograficamente segura em algumas circunstâncias.
thomasrutter
365

Em vez de dividi-lo em campos individuais, é mais fácil gerar um bloco aleatório de dados e alterar as posições de bytes individuais. Você também deve usar um gerador de números aleatórios melhor que o mt_rand ().

De acordo com a RFC 4122 - Seção 4.4 , você precisa alterar estes campos:

  1. time_hi_and_version (bits 4-7 do 7º octeto),
  2. clock_seq_hi_and_reserved (bits 6 e 7 do 9º octeto)

Todos os outros 122 bits devem ser suficientemente aleatórios.

A abordagem a seguir gera 128 bits de dados aleatórios usando openssl_random_pseudo_bytes(), faz as permutações nos octetos e depois usa bin2hex()e vsprintf()faz a formatação 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));

Com o PHP 7, gerar sequências aleatórias de bytes é ainda mais simples usando random_bytes():

function guidv4($data = null)
{
    $data = $data ?? random_bytes(16);
    // ...
}
Ja͢ck
fonte
9
Uma alternativa para usuários * nix que não têm a extensão openssl:$data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16);
Iiridayn 19/04/2013
5
Além disso, eu confiaria muito mais no OpenSSL que no mt_rand.
Prof. Falken
3
O @BrunoAugusto é aleatório e é extremamente improvável (com uma boa fonte aleatória) obter duplicatas, mas é uma boa prática aplicá-la no nível do banco de dados.
Ja͢ck 21/07
9
Existe algum motivo para NÃO colocar a chamada random_bytes (16) dentro da função guidv4 e, portanto, não precisar passar nenhum parâmetro para o guidv4?
Stephen R
7
Pequena melhoria: defina um padrão NULL para $ data e, em seguida, a primeira linha da função é esta: $data = $data ?? random_bytes( 16 ); Agora você pode especificar sua própria fonte de dados aleatória ou deixar a função fazer isso por você. :-)
Stephen R
118

Qualquer pessoa que utilize dependências do compositor , convém considerar esta biblioteca: https://github.com/ramsey/uuid

Não fica mais fácil do que isso:

Uuid::uuid4();
djule5
fonte
32
Ah, eu não sei .... Cinco linhas de código vs. carregar uma biblioteca com dependências? Eu prefiro a função de Jack. YMMV
Stephen R
7
+1 para Stephen. O Ramsey uuid tem muito mais funcionalidade do que apenas o uuid4. Eu não quero banana !, aqui você tem toda a selva!
Lcjury
26
UUIDs não são apenas seqüências aleatórias. Há uma especificação de como funciona. Para gerar um UUID aleatório adequado que não precise me preocupar em ser rejeitado posteriormente, prefiro usar uma biblioteca testada do que rolar minha própria implementação.
Brandon
3
É um UUIDv4. É (principalmente, mas por alguns bits) aleatório. Isso não é criptografia. A paranóia contra "rolar o seu próprio" é bobagem.
6187 Gordon
23

em sistemas unix, use o kernel do sistema para gerar um uuid para você.

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

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

Nota !: Usar esse método para obter um uuid de fato esgota o pool de entropia, muito rapidamente! Eu evitaria usar isso onde seria chamado com frequência.

ThorSummoner
fonte
2
Além da portabilidade, observe que a fonte aleatória é /dev/randomque bloqueia se o pool de entropia estiver esgotado.
Ja͢ck 19/04/14
@Jack: Você poderia, por favor, vincular alguma documentação sobre o tópico esgotamento do pool de entropia em sistemas unix? Eu estaria interessado em saber mais sobre um caso de uso realista em que esse método falha.
ThorSummoner
Não consegui encontrar informações sobre como criar essa fonte de arquivo do kernel especial /dev/urandom, o que, no meu entendimento, não esgotaria, mas correria o risco de retornar uuids duplicados. Eu acho que é uma troca; você realmente precisa de uma identificação única influenciada pela entropia do sistema?
precisa saber é o seguinte
13

Na minha pesquisa para criar um uuid v4, cheguei primeiro a esta página e depois o encontrei em 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

Edit: para esclarecer, esta função sempre fornecerá um uuid v4 (PHP> = 5.3.0).

Quando a função com_create_guid estiver disponível (geralmente apenas no Windows), ela será usada e eliminará os chavetas.

Se não estiver presente (Linux), ele retornará a essa forte função openssl_random_pseudo_bytes aleatória e, em seguida, usará o vsprintf para formatá-lo no v4 uuid.

Arie
fonte
5

Minha resposta é baseada no comentário uniqid user comment, mas ele usa a função openssl_random_pseudo_bytes para gerar uma sequência aleatória em vez de ler a 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
fonte
5

Se você usar, CakePHPpoderá usar o método CakeText::uuid();da classe CakeText para gerar um uuid RFC4122.

bispo
fonte
5

Uma pequena variação na resposta de Jack para adicionar suporte ao 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
fonte
4

Inspirado pela resposta de broofa aqui .

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 se não for possível usar funções 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
fonte
1
Se você olhar para os comentários em outras respostas, verá pessoas dizendo que mt_rand()não há aleatoriedade garantida.
Daniel Cheung
3

Tendo pesquisado exatamente a mesma coisa e quase implementando uma versão disso, pensei que valeria a pena mencionar que, se você estiver fazendo isso dentro de uma estrutura do WordPress , o WP terá sua própria função super útil para exatamente isso:

$myUUID = wp_generate_uuid4();

Você pode ler a descrição e a fonte aqui .

indextwo
fonte
1
A função WP usa mt_rand exclusivamente. Portanto, pode não ter aleatoriedade suficiente
Herbert Peters
@HerbertPeters Você está certo. Eu só mencionei isso porque é uma frase única. Eu diria que seria legal se eles adicionassem um filtro para que você pudesse retornar um número aleatório mais seguro / garantido; mas o outro lado disso é que, se você fosse tão inclinado, você também poderia voltar false🤷
indextwo
2

Que tal usar o mysql para gerar o uuid para você?

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

$query = 'SELECT UUID()';
echo $conn->query($query)->fetch_row()[0];
Hoan Dang
fonte
2
A UUID()função do MySQL cria uuids v1.
staticsan
2
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4));
Cristián Carrasco
fonte
2
Adicione uma explicação ao seu código para ajudar outras pessoas a entender o que ele faz.
KFoobar 16/01
isto é o que realmente foi feito pelo Symfony polyfil - github.com/symfony/polyfill-uuid/blob/master/Uuid.php#L320
Serhii Polishchuk
1

De tom, em 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])
amgina
fonte
3
E se eles não estiverem executando o Unix ou Linux / GNU? Este código não funcionará.
Cole Johnson
4
Isso também tem o potencial de rodar muito devagar se / dev / random estiver vazio e aguardando que mais entropia seja recarregada.
ObsidianX
1
/dev/urandomdeve ser bom - /dev/randomsó deve ser usado para geração de chaves criptográficas de longo prazo.
precisa saber é o seguinte
Com base nisso, eu vim com isso - ele usa várias fontes possíveis de aleatoriedade como substitutos e recorre à semeadura mt_rand()se nada mais sofisticado estiver disponível.
mindplay.dk
1
Até agora, basta usar random_bytes()no PHP 7 e
pronto
1

Tenho certeza de que há uma maneira mais elegante de fazer a conversão de binário em decimal para as porções 4xxxe yxxx. Mas se você deseja usar openssl_random_pseudo_bytescomo seu gerador de números criptograficamente seguro, é isso que eu 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
fonte