Fungsi PHP untuk menghasilkan UUID v4

233

Jadi saya telah melakukan beberapa penggalian di sekitar dan saya telah mencoba untuk mengumpulkan fungsi yang menghasilkan UUID v4 yang valid dalam PHP. Ini adalah yang terdekat yang bisa saya datangi. Pengetahuan saya tentang hex, desimal, biner, operator bitwise PHP dan sejenisnya hampir tidak ada. Fungsi ini menghasilkan UUID v4 yang valid hingga satu area. UUID v4 harus dalam bentuk:

xxxxxxxx- xxxx- 4 xxx- y xxx-xxxxxxxxxxxx

di mana y adalah 8, 9, A, atau B. Di sinilah fungsi gagal karena tidak mematuhi itu.

Saya berharap seseorang dengan pengetahuan lebih dari saya di bidang ini dapat membantu saya dan membantu saya memperbaiki fungsi ini sehingga mematuhi aturan itu.

Fungsinya sebagai berikut:

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

?>

Terima kasih kepada siapa pun yang dapat membantu saya.

anomareh
sumber
5
Jika Anda menggunakan Linux dan jika Anda sedikit malas, Anda dapat membuatnya dengan$newId = exec('uuidgen -r');
JorgeGarza

Jawaban:

282

Diambil dari komentar ini di manual PHP, Anda dapat menggunakan ini:

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
sumber
43
Fungsi ini akan membuat duplikat, jadi hindari ketika Anda membutuhkan nilai unik. Perhatikan bahwa mt_rand () akan selalu menghasilkan urutan angka acak yang sama dengan seed yang sama. Jadi setiap kali seed diulang, UUID yang sama persis dihasilkan. Untuk menyiasatinya, Anda perlu menyemai menggunakan waktu dan alamat mac, tapi saya tidak yakin bagaimana Anda akan melakukan ini, karena mt_srand () memerlukan integer.
Pavle Predic
12
@PavlePredic mt_srand (crc32 (cerita bersambung ([microtime (true), 'USER_IP', 'ETC']))); (Saya wiliam lain: P)
Wiliam
13
Dokumen PHP secara eksplisit mengingatkan bahwa mt_rand () tidak menghasilkan nilai yang aman secara kriptografis. Dengan kata lain, nilai yang dihasilkan oleh fungsi ini dapat diprediksi. Jika Anda perlu memastikan bahwa UUID tidak dapat diprediksi, Anda sebaiknya menggunakan solusi Jack di bawah ini, yang memanfaatkan fungsi openssl_random_pseudo_bytes ().
Richard Keller
7
apa gunanya menghasilkan UUID jika Anda mengisi setiap bidang dengan sampah?
Eevee
1
PHP 7.0+ mendefinisikan fungsi random_bytes () yang akan selalu menghasilkan byte acak yang aman secara kriptografis atau melemparkan pengecualian jika tidak bisa. Ini lebih baik daripada openssl_random_psuedo_bytes () yang hasilnya kadang-kadang tidak aman secara kriptografis dalam beberapa keadaan.
thomasrutter
365

Alih-alih memecahnya menjadi bidang individu, lebih mudah untuk menghasilkan blok data acak dan mengubah posisi byte individu. Anda juga harus menggunakan generator nomor acak yang lebih baik daripada mt_rand ().

Menurut RFC 4122 - Bagian 4.4 , Anda perlu mengubah bidang ini:

  1. time_hi_and_version (bit 4-7 dari oktet 7),
  2. clock_seq_hi_and_reserved (bit 6 & 7 dari 9 oktet)

Semua 122 bit lainnya harus cukup acak.

Pendekatan berikut menghasilkan 128 bit data acak menggunakan openssl_random_pseudo_bytes(), membuat permutasi pada oktet dan kemudian menggunakan bin2hex()dan vsprintf()untuk melakukan pemformatan akhir.

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

Dengan PHP 7, menghasilkan urutan byte acak bahkan lebih sederhana menggunakan random_bytes():

function guidv4($data = null)
{
    $data = $data ?? random_bytes(16);
    // ...
}
Mendongkrak
sumber
9
Alternatif untuk * nix pengguna yang tidak memiliki ekstensi openssl:$data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16);
Iiridayn
5
Juga, saya akan lebih mempercayai OpenSSL daripada mt_rand.
Prof. Falken
3
@ BrunoAugusto itu acak, dan sangat tidak mungkin (dengan sumber acak yang bagus) untuk mendapatkan duplikat, tapi itu praktik yang baik untuk menegakkannya di tingkat basis data.
Ja͢ck
9
Apakah ada alasan untuk TIDAK menempatkan panggilan random_bytes (16) di dalam fungsi guidv4 dan dengan demikian tidak harus meneruskan parameter apa pun ke guidv4?
Stephen R
7
Peningkatan kecil: Tetapkan standar NULL untuk $ data, dan kemudian baris pertama dari fungsi ini adalah ini: $data = $data ?? random_bytes( 16 ); Sekarang Anda BISA menentukan sumber data acak Anda sendiri, atau biarkan fungsi melakukannya untuk Anda. :-)
Stephen R
118

Siapa pun yang menggunakan dependensi komposer , Anda mungkin ingin mempertimbangkan perpustakaan ini: https://github.com/ramsey/uuid

Tidak ada yang lebih mudah dari ini:

Uuid::uuid4();
djule5
sumber
32
Oh, saya tidak tahu .... Lima baris kode vs. memuat perpustakaan dengan dependensi? Saya lebih suka fungsi Jack. YMMV
Stephen R
7
+1 untuk Stephen. Ramsey uuid memiliki lebih banyak fungsi daripada sekadar uuid4. Saya tidak punya pisang! Di sini Anda memiliki seluruh hutan!
lcjury
26
UUID bukan hanya string acak. Ada spesifikasi untuk cara kerjanya. Untuk menghasilkan UUID acak yang tepat dan saya tidak perlu khawatir akan ditolak nanti, saya lebih suka menggunakan perpustakaan yang diuji daripada memutar implementasi saya sendiri.
Brandon
3
Ini adalah UUIDv4. Ini (kebanyakan, tetapi untuk beberapa bit) acak. Ini bukan kriptografi. Paranoia terhadap "menggulirkan milikmu sendiri" itu konyol.
Gordon
23

pada sistem unix, gunakan kernel sistem untuk menghasilkan uuid untuk Anda.

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

Kredit Samveen di https://serverfault.com/a/529319/210994

Catatan !: Menggunakan metode ini untuk mendapatkan uuid benar-benar menghabiskan kumpulan entropi, dengan sangat cepat! Saya akan menghindari menggunakan ini di tempat yang sering disebut.

ThorSummoner
sumber
2
Selain portabilitas, perhatikan bahwa sumber acak adalah /dev/randomyang memblokir jika kumpulan entropi habis.
Ja͢ck
@ Jack Bisakah Anda menautkan beberapa dokumentasi dengan topik kelelahan kolam entropi pada sistem unix? Saya tertarik untuk mengetahui lebih banyak tentang use case yang realistis di mana metode ini rusak.
ThorSummoner
Saya tidak dapat menemukan informasi tentang pembuatan sumber file kernel khusus ini /dev/urandom, yang menurut saya tidak akan melelahkan, tetapi berisiko mengembalikan uuids duplikat. Saya kira itu tradeoff; apakah Anda benar-benar membutuhkan id unik yang dipengaruhi oleh entropi sistem?
ThorSummoner
13

Dalam pencarian saya untuk membuat u4 v4, saya datang pertama ke halaman ini, kemudian menemukan ini di 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));
}

kredit: pavel.volyntsev

Sunting: untuk memperjelas, fungsi ini akan selalu memberi Anda v4 uuid (PHP> = 5.3.0).

Ketika fungsi com_create_guid tersedia (biasanya hanya di Windows), ia akan menggunakannya dan menghapus kurung kurawal.

Jika tidak ada (Linux), itu akan kembali ke fungsi opensl_random_pseudo_bytes acak yang kuat ini, kemudian akan menggunakan vsprintf untuk memformatnya menjadi v4 uuid.

Arie
sumber
5

Jawaban saya didasarkan pada komentar pengguna uniqid komentar tetapi menggunakan fungsi openssl_random_pseudo_bytes untuk menghasilkan string acak, bukan membaca dari/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
sumber
5

Jika Anda menggunakan, CakePHPAnda dapat menggunakan metode mereka CakeText::uuid();dari kelas CakeText untuk menghasilkan uFC RFC4122.

bish
sumber
5

Sedikit variasi pada jawaban Jack untuk menambahkan dukungan untuk 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
sumber
4

Terinspirasi oleh jawaban broofa di sini .

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

Atau jika tidak dapat menggunakan fungsi anonim.

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
sumber
1
Jika Anda melihat komentar di jawaban lain, Anda akan melihat orang mengatakan mt_rand()tidak dijamin keacakan.
Daniel Cheung
3

Setelah mencari hal yang persis sama dan hampir mengimplementasikan versi ini sendiri, saya pikir layak menyebutkan bahwa, jika Anda melakukan ini dalam kerangka WordPress , WP memiliki fungsi super-praktis sendiri untuk hal ini:

$myUUID = wp_generate_uuid4();

Anda dapat membaca deskripsi dan sumbernya di sini .

indextwo
sumber
1
Fungsi WP menggunakan mt_rand secara eksklusif. Jadi mungkin tidak memiliki keacakan yang cukup
Herbert Peters
@HerbertPeters Anda benar. Saya hanya menyebutkannya karena ini adalah one-liner. Saya akan mengatakan bahwa itu akan menjadi rapi jika mereka telah menambahkan filter untuk itu sehingga Anda dapat mengembalikan nomor yang lebih aman / dijamin-acak; tetapi sisi negatifnya adalah bahwa, jika Anda sangat ingin, Anda juga dapat kembali false🤷
indextwo
2

Bagaimana kalau menggunakan mysql untuk menghasilkan uuid untuk Anda?

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

$query = 'SELECT UUID()';
echo $conn->query($query)->fetch_row()[0];
Hoan Dang
sumber
2
UUID()Fungsi MySQL menciptakan v1 uuids.
Statika
2
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4));
Cristián Carrasco
sumber
2
Harap tambahkan penjelasan ke kode Anda untuk membantu orang lain memahami apa yang dilakukannya.
KFoobar
inilah yang sebenarnya dilakukan oleh Symfony polyfil - github.com/symfony/polyfill-uuid/blob/master/Uuid.php#L320
Serhii Polishchuk
1

Dari tom, di 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
sumber
3
Bagaimana jika mereka tidak menjalankan Unix atau Linux / GNU? Kode ini tidak akan berfungsi.
Cole Johnson
4
Ini juga berpotensi berjalan sangat lambat jika / dev / random kosong dan menunggu lebih banyak entropi untuk dimuat ulang.
ObsidianX
1
/dev/urandomharus baik - /dev/randomhanya digunakan untuk pembuatan kunci kriptografi jangka panjang.
Iiridayn
Berdasarkan hal itu, saya datang dengan ini - ia menggunakan beberapa sumber acak untuk mundur, dan resor untuk penyemaian mt_rand()jika tidak ada pelamun yang tersedia.
mindplay.dk
1
Sekarang, cukup gunakan random_bytes()di PHP 7 dan pergilah :-)
mindplay.dk
1

Saya yakin ada cara yang lebih elegan untuk melakukan konversi dari biner ke desimal untuk bagian 4xxxdan yxxx. Tetapi jika Anda ingin menggunakan openssl_random_pseudo_bytessebagai penghasil angka yang aman secara crytografis, inilah yang saya gunakan:

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
sumber