Kiedy powinienem używać uuid.uuid1 () vs. uuid.uuid4 () w pythonie?

207

Rozumiem różnice między tymi dwoma dokumentami.

uuid1():
Wygeneruj identyfikator UUID na podstawie identyfikatora hosta, numeru sekwencji i bieżącej godziny

uuid4():
Wygeneruj losowy UUID.

uuid1Używa więc informacji o maszynie / sekwencji / czasie do wygenerowania UUID. Jakie są zalety i wady korzystania z nich?

Wiem, że uuid1()mogą mieć obawy dotyczące prywatności, ponieważ są oparte na informacjach o maszynie. Zastanawiam się, czy jest coś bardziej subtelnego przy wyborze jednego lub drugiego. Właśnie używam uuid4()teraz, ponieważ jest to całkowicie losowy UUID. Ale zastanawiam się, czy powinienem używać, uuid1aby zmniejszyć ryzyko kolizji.

Zasadniczo szukam wskazówek ludzi na temat najlepszych praktyk korzystania z jednego kontra drugiego. Dzięki!

Rocketmonkeys
źródło
3
Oto alternatywne podejście do UUID. Chociaż prawdopodobieństwo kolizji jest nieskończenie małe, UUID nie gwarantuje wyjątkowości. Aby zagwarantować unikalność, możesz użyć klucza złożonego jako [<identyfikator systemu>, <identyfikator lokalny>]. Każdy system uczestniczący w udostępnianiu danych musi mieć swój własny unikalny identyfikator systemu albo przypisany podczas konfiguracji systemu, albo uzyskany ze wspólnej puli identyfikatorów. Lokalny identyfikator to unikalny identyfikator w dowolnym systemie. Wiąże się to z większymi problemami, ale gwarantuje wyjątkowość. Przepraszam za offtopic, tylko próbuję pomóc.
o
3
Nie przejmuje się „wspomnieniami o prywatności”, o których wspomniał
Shrey

Odpowiedzi:

253

uuid1()gwarantuje, że nie spowoduje żadnych kolizji (przy założeniu, że nie tworzysz ich zbyt wielu jednocześnie). Nie użyłbym go, jeśli ważne jest, aby nie istniało połączenie między uuidkomputerem a komputerem, ponieważ adres mac przyzwyczaja się do tego, aby był unikalny na różnych komputerach.

Możesz utworzyć duplikaty, tworząc więcej niż 2 14 uuid1 w czasie krótszym niż 100ns, ale nie jest to problem w większości przypadków użycia.

uuid4()generuje, jak powiedziałeś, losowy UUID. Szansa na kolizję jest naprawdę bardzo mała. Na tyle mały, że nie powinieneś się tym martwić. Problem polega na tym, że zły generator liczb losowych zwiększa prawdopodobieństwo kolizji.

Ta doskonała odpowiedź Boba Amana ładnie podsumowuje. (Polecam przeczytać całą odpowiedź).

Szczerze mówiąc, w obszarze pojedynczej aplikacji bez złośliwych aktorów wyginięcie całego życia na Ziemi nastąpi na długo przed kolizją, nawet w przypadku UUID w wersji 4, nawet jeśli generujesz całkiem sporo UUID na sekundę.

Georg Schölly
źródło
Przepraszam, skomentowałem bez dokładnego zbadania - są zarezerwowane bity, aby nie dopuścić do zderzenia identyfikatora UUID wersji 4 z UUID wersji 1. Usunę mój oryginalny komentarz. Zobacz tools.ietf.org/html/rfc4122
Mark Ransom,
1
@ gs Tak, ma sens z tym, co czytałem. uuid1 jest „bardziej unikalny”, podczas gdy uuid4 jest bardziej anonimowy. Zasadniczo używaj uuid1, chyba że masz powód, aby tego nie robić. @mark okup: Niesamowita odpowiedź, nie pojawiła się, gdy szukałem uuid1 / uuid4. Wygląda na to, że prosto z pyska konia.
rocketmonkeys,
6
uuid1niekoniecznie będzie produkować unikalne UUID, jeśli produkujesz kilka na sekundę w tym samym węźle. Przykład: [uuid.uuid1() for i in range(2)]. Chyba że dzieje się coś dziwnego, czego mi brakuje.
Michael Mior
1
@Michael: uuid1ma numer sekwencyjny (czwarty element w twoim przykładzie), więc jeśli nie wykorzystasz wszystkich bitów licznika, nie dojdzie do kolizji.
Georg Schölly,
3
@Michael: Próbowałem zbadać okoliczności, w których dochodzi do kolizji i dodałem informacje, które znalazłem.
Georg Schölly,
32

Jednym z przykładów, w którym można rozważyć uuid1(), uuid4()jest przypadek, gdy UUID są tworzone na osobnych komputerach , na przykład gdy wiele transakcji online jest przetwarzanych na kilku komputerach w celu skalowania.

W takiej sytuacji ryzyko kolizji z powodu złych wyborów w sposobie inicjowania generatorów liczb pseudolosowych, na przykład, a także potencjalnie większa liczba wyprodukowanych identyfikatorów UUID, zwiększa prawdopodobieństwo utworzenia duplikatów identyfikatorów.

Innym zainteresowaniem uuid1()w tym przypadku jest to, że maszyna, na której każdy identyfikator GUID został początkowo utworzony, jest domyślnie rejestrowana (w części UUID dotyczącej „węzła”). To i informacje o czasie mogą pomóc choćby w debugowaniu.

mjv
źródło
20

Mój zespół po prostu miał problemy z użyciem UUID1 do skryptu aktualizacji bazy danych, w którym wygenerowaliśmy ~ 120 000 UUID w ciągu kilku minut. Kolizja UUID doprowadziła do naruszenia ograniczenia klucza podstawowego.

Uaktualniliśmy setki serwerów, ale w naszych instancjach Amazon EC2 napotkaliśmy ten problem kilka razy. Podejrzewam, że niska rozdzielczość zegara i przejście na UUID4 rozwiązało to za nas.

Mattias Lagergren
źródło
5

Należy zwrócić uwagę na jedną rzecz uuid1, jeśli używasz domyślnego wywołania (bez podania clock_seqparametru), masz szansę na zderzenie: masz tylko 14 bitów losowości (wygenerowanie 18 wpisów w ciągu 100ns daje z grubsza 1% szansy na kolizję zobacz paradoks / atak urodzinowy). Problem nigdy nie wystąpi w większości przypadków użycia, ale na maszynie wirtualnej ze słabą rozdzielczością zegara cię ugryzie.

Guillaume
źródło
7
@Guilaume przydałby się przykład dobrej praktyki z wykorzystaniem clock_seq....
eric
@Guilaume Jak obliczyłeś tę szansę 1%? 14 bitów losowości oznacza, że ​​kolizja na pewno się zdarzy, jeśli wygenerujesz> = 2 ^ 14 identyfikatorów na 100ns, a to oznacza, że ​​1% szansy na kolizję ma miejsce, gdy wyprodukujesz około 163 identyfikatory na 100 ns
maks.
1
@maks Jak powiedziałem, powinieneś spojrzeć na paradoks urodzinowy .
Guillaume,
3

Być może coś, o czym nie wspomniano, dotyczy lokalności.

Adres MAC lub kolejność na podstawie czasu (UUID1) może pozwolić na zwiększenie wydajności bazy danych, ponieważ mniejszą ilością pracy jest sortowanie liczb bliżej siebie niż liczb losowych (UUID4) (patrz tutaj ).

Drugim powiązanym problemem jest to, że używanie UUID1 może być przydatne w debugowaniu, nawet jeśli dane źródłowe zostaną utracone lub nie zostaną wyraźnie zapisane (jest to oczywiście sprzeczne z kwestią prywatności wspomnianą przez PO).

cz
źródło
1

Oprócz zaakceptowanej odpowiedzi istnieje trzecia opcja, która może być przydatna w niektórych przypadkach:

v1 z losowym MAC („v1mc”)

Możesz stworzyć hybrydę między wersją v1 i v4, celowo generując identyfikatory UUID v1 z losowym adresem MAC emisji (jest to dozwolone w specyfikacji v1). Wynikowy identyfikator UUID v1 jest zależny od czasu (jak zwykły v1), ale brakuje mu wszystkich informacji specyficznych dla hosta (jak v4). Jest również znacznie bliższy v4 pod względem odporności na zderzenia: v1mc = 60 bitów czasu + 61 losowych bitów = 121 unikalnych bitów; v4 = 122 losowe bity.

Pierwsze miejsce, w którym się z tym spotkałem, to funkcja uuid_generate_v1mc () Postgresa . Od tego czasu użyłem następującego odpowiednika Pythona:

from os import urandom
from uuid import uuid1
_int_from_bytes = int.from_bytes  # py3 only

def uuid1mc():
    # NOTE: The constant here is required by the UUIDv1 spec...
    return uuid1(_int_from_bytes(urandom(6), "big") | 0x010000000000)

(uwaga: mam dłuższą + szybszą wersję, która bezpośrednio tworzy obiekt UUID; może publikować, jeśli ktoś chce)


W przypadku DUŻYCH ilości połączeń / sekundę może to potencjalnie wyczerpać losowość systemu. Państwo mogli skorzystać z stdlib randomzamiast modułu (prawdopodobnie będzie to również szybciej). Ale UWAGA: potrzeba tylko kilkuset UUID, aby atakujący mógł określić stan RNG, a tym samym częściowo przewidzieć przyszłe UUID.

import random
from uuid import uuid1

def uuid1mc_insecure():
    return uuid1(random.getrandbits(48) | 0x010000000000)
Eli Collins
źródło
Wygląda na to, że ta metoda jest „podobna” do v4 (niezależna od gospodarza), ale gorsza (mniej bitów, zależność od urandomu itp.). Czy są jakieś zalety w porównaniu do samego uuid4?
rocketmonkeys,
Jest to przede wszystkim aktualizacja dla przypadków, w których wersja 1 jest przydatna ze względu na swoje właściwości czasowe, ale pożądana jest większa odporność na kolizje i prywatność hosta. Jednym z przykładów jest klucz podstawowy dla bazy danych - w porównaniu z wersją v4, uuids v1 będą miały lepszą lokalizację podczas zapisywania na dysku, będą miały bardziej użyteczny rodzaj naturalny itp. Ale jeśli masz przypadek, w którym atakujący przewiduje 2 ** 61 bitów to problem z bezpieczeństwem (np. Jak uuid nonce), a następnie $ diety tak, zamiast tego użyj uuid4 (wiem, że tak!). Re: będąc gorszym, ponieważ używa urandom, nie jestem pewien, co masz na myśli - w Pythonie uuid4 () również używa urandom.
Eli Collins
Dobre rzeczy, to ma sens. Dobrze jest zobaczyć nie tylko to, co możesz zrobić (swój kod), ale także dlaczego tego chcesz. Re: urandom, to znaczy, że zużywasz 2x losowość (1 dla uuid1, inny dla urandom), więc szybciej zużyłbym entropię systemu.
rocketmonkeys
W rzeczywistości jest to o połowę mniej niż uuid4: uuid1 () używa 14 bitów dla clock_seq, co zaokrągla w górę do 2 bajtów urandomu. Opakowanie uuid1mc wykorzystuje 48 bitów, które powinny być odwzorowane na 6 bajtów urandom, co daje całkowitą wartość zużytego (8) na połączenie. podczas gdy uuid4 bezpośrednio wywołuje urandom (16) dla każdego połączenia.
Eli Collins