Функція PHP для створення v4 UUID


233

Тож я займався копанням і намагався скласти функцію, яка генерує дійсний U4 UUID v4 в PHP. Це найближче, що я зміг підійти. Мої знання з шістнадцяткових, десяткових, двійкових, бітових операторів PHP тощо, майже не існує. Ця функція генерує дійсний v4 UUID до однієї області. UUID v4 має бути у формі:

xxxxxxxx- xxxx- 4 xxx- y xxx-xxxxxxxxxxxx

де y дорівнює 8, 9, A або B. Ось де функції відмовляються, оскільки вони не дотримуються цього.

Я сподівався, що хтось, хто має більше знань, ніж я, у цій галузі, міг би мені подати руку і допомогти мені виправити цю функцію, щоб він дотримувався цього правила.

Функція така:

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

?>

Дякую всім, хто може мені допомогти.


5
Якщо ви користуєтесь Linux і якщо ви трохи ліниві, ви можете генерувати їх$newId = exec('uuidgen -r');
JorgeGarza

Відповіді:


282

З огляду на цей коментар до керівництва PHP, ви можете скористатися цим:

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
Ця функція буде створювати дублікати, тому слід уникати його , коли вам потрібно унікальні значення. Зауважте, що mt_rand () завжди створюватиме однакову послідовність випадкових чисел, що даються тим самим насінням. Тому щоразу, коли насіння повторюється, генерується однаковий точний UUID. Щоб обійти це, вам потрібно буде викласти це за допомогою адреси часу та mac, але я не впевнений, як би ви це зробили, оскільки mt_srand () вимагає цілого числа.
Павло Продик

12
@PavlePredic mt_srand (crc32 (серіалізувати ([microtime (вірно), 'USER_IP', 'ETC']))); (Я інший wiliam: P)
Wiliam

13
Документи PHP явно застерігають, що mt_rand () не генерує криптографічно захищені значення. Іншими словами, значення, породжені цією функцією, можуть бути передбачуваними. Якщо вам потрібно переконатися, що UUID не передбачувані, скоріше скористайтеся рішенням Джека нижче, яке використовує функцію openssl_random_pseudo_bytes ().
Річард Келлер

7
що на землі має сенс генерувати UUID, якщо ви заповнюєте кожне поле сміттям?
Eevee

1
PHP 7.0+ визначає функцію random_bytes (), яка завжди генерує криптографічно захищені випадкові байти або викидає виняток, якщо цього не вдається. Це краще, ніж навіть openssl_random_psuedo_bytes (), вихід якого за певних обставин іноді не є криптографічно захищеним.
thomasrutter

365

Замість того, щоб розділити їх на окремі поля, простіше генерувати випадковий блок даних та змінювати окремі позиції байтів. Ви також повинні використовувати кращий генератор випадкових чисел, ніж mt_rand ().

Відповідно до RFC 4122 - Розділ 4.4 , вам потрібно змінити ці поля:

  1. time_hi_and_version (біти 4-7 7-го октету),
  2. clock_seq_hi_and_reserved (біти 6 та 7 9-го октету)

Всі інші 122 біти повинні бути достатньо випадковими.

Наступний підхід генерує 128 біт випадкових даних за допомогою openssl_random_pseudo_bytes(), робить перестановки на октети, а потім використовує bin2hex()та vsprintf()робить остаточне форматування.

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

З PHP 7 генерування випадкових послідовностей байтів ще простіше за допомогою random_bytes():

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

9
Альтернатива для * nix користувачів, які не мають розширення openssl:$data = file_get_contents('/dev/urandom', NULL, NULL, 0, 16);
Iiridayn

5
Також я б довіряв OpenSSL набагато більше, ніж mt_rand.
Проф. Фолкен

3
@BrunoAugusto - це випадково, і навряд чи (з хорошим випадковим джерелом) отримати дублікати, але добре застосовувати це на рівні бази даних.
Ja͢ck

9
Чи є якась причина НЕ ставити виклик random_bytes (16) всередині функції guvv4 і, отже, не потрібно передавати жодний параметр guvv4?
Stephen R

7
Невелике поліпшення: встановіть значення NULL за замовчуванням для $ data, і тоді перший рядок функції такий: $data = $data ?? random_bytes( 16 ); Тепер ви можете Вказати власне випадкове джерело даних або дозволити функції зробити це за вас. :-)
Stephen R

118

Кожен, хто використовує композиторські залежності, можливо, захочете розглянути цю бібліотеку: https://github.com/ramsey/uuid

Це не стає простіше, ніж це:

Uuid::uuid4();

32
О, я не знаю .... П'ять рядків коду проти завантаження бібліотеки залежними? Я віддаю перевагу функції Джека. YMMV
Stephen R

7
+1 до Стівена. Ramsey uuid має набагато більше функціональних можливостей, ніж просто uuid4. Я не хочу банан! Тут у вас є цілі джунглі!
lcjury

26
UUID - це не просто випадкові рядки. Існує специфікація того, як це працює. Щоб створити належний випадковий UUID, який мені не доведеться турбуватися про відхилення пізніше, я б скоріше скористався протестованою бібліотекою, ніж згортав власну реалізацію.
Брендон

3
Це UUIDv4. Це (в основному, але на кілька біт) випадковим чином. Це не криптографія. Параноїя проти "катання свого" - нерозумно.
Гордон

23

в системах Unix використовуйте системне ядро ​​для створення uuid для вас.

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

Кредит Samveen на https://serverfault.com/a/529319/210994

Примітка!: Використання цього методу для отримання uuid насправді виснажує пул ентропії дуже швидко! Я б уникав цього використовувати там, де його часто називали.


2
Крім портативності, зауважте, що випадкове джерело є тим, /dev/randomщо блокується, якщо пул ентропії вичерпано.
Ja͢ck

@Jack Будь ласка, зв’яжіть будь-яку документацію з теми вичерпання пулу ентропії на системах Unix, будь ласка? Мені буде цікаво дізнатись більше про реалістичний випадок використання, коли цей метод руйнується.
ThorSummoner

Мені не вдалося знайти інформацію про створення цього спеціального джерела файлу ядра /dev/urandom, яке, на моє розуміння, не вичерпає, але ризикує повернути дублікати uuids. Я здогадуюсь його компромісу; чи справді вам потрібен унікальний ідентифікатор, на який впливає ентропія системи?
ThorSummoner

13

Під час пошуку створення u4 uuid я спершу зайшов на цю сторінку, а потім знайшов це на 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));
}

кредит: пав.волинцев

Редагувати: щоб уточнити, ця функція завжди надасть вам v4 uuid (PHP> = 5.3.0).

Коли функція com_create_guid доступна (як правило, лише в Windows), вона буде використовувати це і знімати фігурні дужки.

Якщо немає (Linux), він повернеться до цієї сильної випадкової функції openssl_random_pseudo_bytes, після чого він буде використовувати vsprintf, щоб відформатувати її у v4 uuid.


5

Моя відповідь заснована на коментарі користувача uniqid коментаря, але він використовує функцію openssl_random_pseudo_bytes для генерації випадкових рядків замість читання з/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

Якщо ви використовуєте, CakePHPви можете використовувати їх метод CakeText::uuid();з класу CakeText для створення RFC4122 uuid.


5

Незначна зміна відповіді Джека, щоб додати підтримку 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

Натхненний broofa відповідь «s тут .

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

Або якщо немає можливості використовувати анонімні функції.

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
Якщо ви подивитесь на коментарі в інших відповідях, ви побачили, що люди говорять mt_rand(), що випадковість не гарантована.
Даніель Чен

3

Шукаючи саме таку саму річ і майже самостійно впроваджуючи версію цього, я подумав, що варто згадати, що якщо ви робите це в рамках WordPress , WP має власну супер-зручну функцію саме для цього:

$myUUID = wp_generate_uuid4();

Опис та джерело ви можете прочитати тут .


1
Функція WP використовує виключно mt_rand. Тому може не вистачити випадковості
Герберт Пітерс

@HerbertPeters Ти маєш рацію. Я згадував це лише тому, що це однолінійний. Я збирався сказати, що було б акуратно, якби вони додали для нього фільтр, щоб ви могли повернути більш безпечне / гарантовано-випадкове число; але зворотний бік цього полягає в тому, що, якби ти був так схильний, ти також falseможеш повернутися 🤷
indextwo

2

Як щодо використання mysql для створення uuid для вас?

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

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

2
UUID()Функція MySQL створює v1 uuids.
статикан


1

З тома, на 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
Що робити, якщо вони не працюють Unix або Linux / GNU? Цей код не працюватиме.
Коул Джонсон

4
Це також має потенціал працювати дуже повільно, якщо / dev / random порожній і чекає ще ентропії для перезавантаження.
ObsidianX

1
/dev/urandomмає бути добре - /dev/randomслід використовувати лише для генерації довгострокових криптографічних ключів.
Ірідайн

Виходячи з цього, я придумав це - він використовує декілька можливих джерел випадковості як резервні копії , і вдається до висіву насіння, mt_rand()якщо нічого фантазії немає.
mindplay.dk

1
На даний момент просто використовуйте random_bytes()в PHP 7 і ви переходите :-)
mindplay.dk

1

Я впевнений, що є більш елегантний спосіб здійснити перетворення з двійкового в десятковий для 4xxxі yxxxчастини. Але якщо ви хочете використовувати openssl_random_pseudo_bytesяк криптографічно захищений генератор чисел, це я використовую:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.