Как генерируются UUID / Хабр
Вы наверняка уже использовали в своих проектах UUID и полагали, что они уникальны. Давайте рассмотрим основные аспекты реализации и разберёмся, почему UUID практически уникальны, поскольку существует мизерная возможность возникновения одинаковых значений.
Современную реализацию UUID можно проследить до RFC 4122, в котором описано пять разных подходов к генерированию этих идентификаторов. Мы рассмотрим каждый из них и пройдёмся по реализации версии 1 и версии 4.
Теория
UUID (universally unique IDentifier) — это 128-битное число, которое в разработке ПО используется в качестве уникального идентификатора элементов. Его классическое текстовое представление является серией из 32 шестнадцатеричных символов, разделённых дефисами на пять групп по схеме 8-4-4-4-12.
Например:
3422b448-2460-4fd2-9183-8000de6f8343
Информация о реализации UUID встроена в эту, казалось бы, случайную последовательность символов:
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
Значения на позициях M и N определяют соответственно версию и вариант UUID.
Версия
Номер версии определяется четырьмя старшими битами на позиции М. На сегодняшний день существуют такие версии:
Вариант
Это поле определяет шаблон информации, встроенной в UUID. Интерпретация всех остальных битов в UUID зависит от значения варианта.
Мы определяем его по первым 1-3 старшим битам на позиции N.
Сегодня чаще всего используют вариант 1, при котором MSB0
равняется 1
, а MSB1
равняется 0
. Это означает, что с учётом подстановочных знаков — битов, отмеченных х
— единственными возможными значениями будут 8
, 9
, A
или B
.
Памятка:1 0 0 0 = 8
1 0 0 1 = 9
1 0 1 0 = A
1 0 1 1 = B
Так что если вы видите UUID с такими значениями на позиции N, то это идентификатор в варианте 1.
Версия 1 (время + уникальный или случайный идентификатор хоста)
В этом случае UUID генерируется так: к текущему времени добавляется какое-то идентифицирующее свойство устройства, которое генерирует UUID, чаще всего это MAC-адрес (также известный как ID узла).
Идентификатор получают с помощью конкатенации 48-битного МАС-адреса, 60-битной временной метки, 14-битной «уникализированной» тактовой последовательности, а также 6 битов, зарезервированных под версию и вариант UUID.
Тактовая последовательность — это просто значение, инкрементируемое при каждом изменении часов.
Временная метка, которая используется в этой версии, представляет собой количество 100-наносекундных интервалов с 15 октября 1582 года — даты возникновения григорианского календаря.
Возможно, вы знакомы с принятым в Unix-системах исчислением времени с начала эпохи. Это просто другая разновидность Нулевого дня. В сети есть сервисы, которые помогут вам преобразовать одно временное представление в другое, так что не будем на этом останавливаться.
Хотя эта реализация выглядит достаточно простой и надёжной, однако использование MAC-адреса машины, на которой генерируется идентификатор, не позволяет считать этот метод универсальным. Особенно, когда главным критерием является безопасность. Поэтому в некоторых реализациях вместо идентификатора узла используется 6 случайных байтов, взятых из криптографически защищённого генератора случайных чисел.
Сборка UUID версии 1 происходит так:
- Берутся младшие 32 бита текущей временной метки UTC. Это будут первые 4 байта (8 шестнадцатеричных символов) UUID [
TimeLow
]. - Берутся средние 16 битов текущей временной метки UTC. Это будут следующие 2 байта (4 шестнадцатеричных символа) [
TimeMid
]. - Следующие 2 байта (4 шестнадцатеричных символа) конкатенируют 4 бита версии UUID с оставшимися 12 старшими битами текущей временной метки UTC (в которой всего 60 битов) [
]. - Следующие 1-3 бита определяют вариант версии UUID. Оставшиеся биты содержат тактовую последовательность, которая вносит небольшую долю случайности в эту реализацию. Это позволяет избежать коллизий, когда в одной системе работает несколько UUID-генераторов: либо системные часы переводятся назад для генератора, либо изменение времени замедляется [
ClockSequenceHiAndRes && ClockSequenceLow
]. - Последние 6 байтов (12 шестнадцатеричных символов, 48 битов) — это «идентификатор узла», в роли которого обычно выступает MAC-адрес генерирующего устройства [
NodeID
].
UUID версии 1 генерируется с помощью конкатенации:
TimeLow + TimeMid + TimeHighAndVersion + (ClockSequenceHiAndRes && ClockSequenceLow) + NodeID
Поскольку эта реализация зависит от часов, нам нужно обрабатывать пограничные ситуации. Во-первых, для минимизации коррелирования между системами по умолчанию тактовая последовательность берётся как случайное число — так делается лишь один раз за весь жизненный цикл системы. Это даёт нам дополнительное преимущество: поддержку идентификаторов узлов, которые можно переносить между системами, поскольку начальное значение тактовой последовательности совершенно не зависит от идентификатора узла.
Помните, что главная цель использования тактовой последовательности — внести долю случайности в наше уравнение. Биты тактовой последовательности помогают расширить временную метку и учитывать ситуации, когда несколько UUID генерируются ещё до того, как изменяются процессорные часы. Так мы избегаем создания одинаковых идентификаторов, когда часы переводятся назад (устройство выключено) или меняется идентификатор узла. Если часы переведены назад, или могли быть переведены назад (например, пока система была отключена), и UUID-генератор не может убедиться, что идентификаторы сгенерированы с более поздними временными метками по сравнению с заданным значением часов, тогда нужно изменить тактовую последовательность. Если нам известно её предыдущее значение, его можно просто увеличить; в противном случае его нужно задать случайным образом или с помощью высококачественного ГПСЧ.
Версия 2 (безопасность распределённой вычислительной среды)
Главное отличие этой версии от предыдущей в том, что вместо «случайности» в виде младших битов тактовой последовательности здесь используется идентификатор, характерный для системы. Часто это просто идентификатор текущего пользователя. Версия 2 используется реже, она очень мало отличается от версии 1, так что идём дальше.
Версия 3 (имя + MD5-хэш)
Если нужны уникальные идентификаторы для информации, связанной с именами или наименованием, то для этого обычно используют UUID версии 3 или версии 5.
Они кодируют любые «именуемые» сущности (сайты, DNS, простой текст и т.д.) в UUID-значение. Самое важное — для одного и того же namespace или текста будет сгенерирован такой же UUID.
Обратите внимание, что namespace сам по себе является UUID.
let namespace = “digitalbunker.dev” let namespaceUUID = UUID3(.DNS, namespace) // Ex: UUID3(namespaceUUID, “/category/things-you-should-know-1/”) 4896c91b-9e61-3129-87b6-8aa299028058 UUID3(namespaceUUID, “/category/things-you-should-know-2/”) 29be0ee3-fe77-331e-a1bf-9494ec18c0ba UUID3(namespaceUUID, “/category/things-you-should-know-3/”) 33b06619-1ee7-3db5-827d-0dc85df1f759
Важно понимать, что ни namespace, ни входное имя не могут быть вычислены на основе UUID. Это необратимая операция. Единственное исключение — брутфорс, когда одно из значений (namespace или текст) уже известно атакующему.
При одних и тех же входных данных генерируемые UUID версий 3 и 5 будут детерминированными.
Версия 4 (ГПСЧ)
Самая простая реализация.
6 битов зарезервированы под версию и вариант, остаётся ещё 122 бита. В этой версии просто генерируется 128 случайных битов, а потом 6 из них заменяется данными о версии и варианте.
Такие UUID полностью зависят от качества ГПСЧ (генератора псевдослучайных чисел). Если его алгоритм слишком прост, или ему не хватает начальных значений, то вероятность повторения идентификаторов возрастает.
В современных языках чаще всего используются UUID версии 4.
Её реализация достаточно простая:
- Генерируем 128 случайных битов.
- Переписываем некоторые биты корректной информацией о версии и варианте:
- Берём седьмой бит и выполняем c
0x0F
операцию AND для очистки старшего полубайта. А затем выполняем с0x40
операцию OR для назначения номера версии 4. - Затем берём девятый байт, выполняем c
0x3F
операцию AND и с0x80
операцию OR.
- Берём седьмой бит и выполняем c
- Преобразуем 128 битов в шестнадцатеричный вид и вставляем дефисы.
Версия 5 (имя + SHA-1-хэш)
Практика
Одним из важных достоинств UUID является то, что их уникальность не зависит от центрального авторизующего органа или от координации между разными системами. Кто угодно может создать UUID с определённой уверенностью в том, что в обозримом будущем это значение больше никем не будет сгенерировано.
Это позволяет комбинировать в одной БД идентификаторы, созданные разными участниками, или перемещать идентификаторы между базами с ничтожной вероятностью коллизии.
UUID можно использовать в качестве первичных ключей в базах данных, в качестве уникальных имён загружаемых файлов, уникальных имён любых веб-источников. Для их генерирования вам не нужен центральный авторизующий орган. Но это обоюдоострое решение. Из-за отсутствия контролёра невозможно отслеживать сгенерированные UUID.
Есть и ещё несколько недостатков, которые нужно устранить. Неотъемлемая случайность повышает защищённость, однако она усложняет отладку. Кроме того, UUID может быть избыточным в некоторых ситуациях. Скажем, не имеет смысла использовать 128 битов для уникальной идентификации данных, общий размер которых меньше 128 битов.
Уникальность
Может показаться, что если у вас будет достаточно времени, то вы сможете повторить какое-то значение. Особенно в случае с версией 4. Но в реальности это не так. Если бы вы генерировали один миллиард UUID в секунду в течение 100 лет, то вероятность повторения одного из значений была бы около 50 %. Это с учётом того, что ГПСЧ обеспечивает достаточное количество энтропии (истинная случайность), иначе вероятность появления дубля будет выше. Более наглядный пример: если бы вы сгенерировали 10 триллионов UUID, то вероятность появления двух одинаковых значений равна 0,00000006 %.
А в случае с версией 1 часы обнулятся только в 3603 году. Так что если вы не планируете поддерживать работу своего сервиса ещё 1583 года, то вы в безопасности.
Впрочем, вероятность появления дубля остаётся, и в некоторых системах стараются это учитывать. Но в подавляющем большинстве случаев UUID можно считать полностью уникальными. Если вам нужно больше доказательств, вот простая визуализация вероятности коллизии на практике.
Обработка ошибок — VKBottle
Иногда при запросах к api вконтакте возникают ошибки, для их обработки во фреймворке предусмотрено несколько инструментов
try .
.. except VKAPIErrorДля начала разъясним что такое VKAPIError
, это подтип CodeException
, особенность которого заключается в том, чтобы ошибка идентифицировалась в except и без указанного кода (try except VKAPIError
) и при указании кода (try except VKAPIError[code]
)
В VKAPIError
есть два поля:
code
— код ошибки, intdescription
— описание ошибки, str
Чтобы использовать VKAPIError
нужно импортировать его:
from vkbottle import VKAPIError
Теперь можно обернуть какой нибудь код с запросом к API:
try: await api.wall.post() except VKAPIError as e: print("Возникла ошибка", e.code)
При исполнении этого кода vk вернет ошибку, из-за того что не были переданы нужные параметры
try … except VKAPIError[code]
Кроме более общего способа для обработки всех ошибок vk в одном блоке except, вы можете воспользоваться более конкретным VKAPIError[code]
Сделаем несколько блоков для демонстрации того, как первый и второй способ можно комбинировать
try: await api. messages.send(peer_id=1, message="привет!", random_id=0) except VKAPIError[902] as e: print("не могу отправить сообщение из-за настроек приватности") except VKAPIError as e: print("не могу отправить:", e.error_description)
Специфичные ошибки
Некоторые ошибки vk имеют дополнительные поля, которые могут понадобиться вам для их обработки:
CaptchaError
:sid
— идентификатор captcha, intimg
— ссылка на изображение, str
ErrorHandler
У ErrorHandler
есть 4 метода:
register_error_handler
— декоратор, принимающий типы ошибок и асинхронную функцию-хендлер. Если возникнет одна из указанных ошибок (или её подтипа), исполнится указанный хендлер.register_undefined_error_handler
— декоратор, принимающий тип ошибки и асинхронную функцию-хендлер. Если возникнет неизвестная ошибка, исполнится этот хендлер.handle
, принимающий экземпляр ошибки. Передаёт ошибку в соответствующий хендлер, зарегистрированный с помощью вышеописанных декораторов. Если для данной ошибки нет хендлера, поднимает её.catch
— декоратор. Ловит ошибки из декорированной функции и передаёт их методуhandle
.
from vkbottle import Bot, VKAPIError bot = Bot("token") @bot.error_handler.register_error_handler(RuntimeError) async def runtime_error_handler(e: RuntimeError): print("возникла ошибка runtime", e) @bot.error_handler.register_error_handler(VKAPIError[902]) async def unable_to_write_handler(e: VKAPIError): print("человек не разрешил отправлять сообщения", e)
В примере создано 2 хендлера ошибок: для RuntimeError
и конкретизированного VKAPIError
Также можно регистрировать хендлеры сразу для нескольких типов ошибок:
from typing import Union from vkbottle import Bot, VKAPIError bot = Bot("token") @bot.error_handler.register_error_handler(TypeError, ValueError) async def type_or_value_error_handler(e: Union[TypeError, ValueError]): print("возникла ошибка type или value", e) @bot. error_handler.register_error_handler(*VKAPIError[6, 9]) async def limit_reached_write_handler(e: VKAPIError): print("ой, слишком много запросов", e)
Ещё у ErrorHandler
есть параметр redirect_arguments: bool
, который позволяет передавать в хендлер аргументы из задекорированной с помощью catch
функции. В связке с ботом позволяет передать в хендлер контекстные аргументы из правил и мидлварей.
swear
Краткий вариант хендлера ошибок одним декоратором, важно что swear может декорировать как синхронные, так и асинхронные функции
from vkbottle import swear
В swear необходимо первым аргументом передать ошибку, которую он будет хендлить, например тот же RuntimeError
и на выбор:
exception_handler=func
— если у вас есть хендлер которым вы будете хендлить ошибку и возвращать из него нужное значение для функцииjust_return=True
— если вам нужно просто вернуть ошибку из функцииjust_log=True
— если вам нужно логировать ошибку и вернуть из декорированной функцииNone
from vkbottle import swear @swear(RuntimeError, just_return=True) def my_func(): raise RuntimeError("error") my_func() # RuntimeError("error")
[PATCH v4 00/16] Добавить драйвер Broadcom VK
Эта серия исправлений удаляет предыдущие исправления в [1] которые были включены Кисом Куком в серию патчей «Ввести частичную поддержку kernel_read_file()» [2].
Остальные исправления содержатся в этой серии для добавления драйвера Broadcom VK. (что зависит от добавления API request_firmware_into_buf в другие серии исправлений [2] применяются первыми). Обратите внимание, что эта серия исправлений не будет компилироваться без [2]. [1] https://lore.kernel.org/lkml/[email protected]/ [2] https://lore.kernel.org/lkml/[email protected]/ Изменения по сравнению с v3: - разделить драйвер на более добавочные коммиты для принятия/проверки - некоторые dev_info снижены до dev_dbg - удалить типы ANSI stdint и заменить их типами linux u8 и т. д. - изменен возврат EIO на EPFNOSUPPORT - переместите vk_msg_cmd внутрь драйвера, чтобы не подвергать UAPI в это время Изменения по сравнению с версией 2: - макрос открытого кода BIT в заголовке uapi - Улучшения загрузки A0/B0 Изменения по сравнению с версией 1: - объявить bcm_vk_intf_ver_chk статическим Скотт Бранден (16): bcm-vk: добавить bcm_vk UAPI разное: bcm-vk: добавить драйвер Broadcom VK разное: bcm-vk: добавить поддержку автозагрузки misc: bcm-vk: добавить другое устройство в драйвер Broadcom VK разное: bcm-vk: добавить триггеры при панике хоста или перезагрузке для уведомления карты разное: bcm-vk: добавить поддержку ttyVK разное: bcm-vk: добавить открытие/выпуск разное: bcm-vk: добавить ioctl load_image разное: bcm-vk: добавить get_card_info, peerlog_info и proc_mon_info разное: bcm-vk: добавить поддержку обмена сообщениями ВКонтакте разное: bcm-vk: поддержка reset_pid разное: bcm-vk: добавить BCM_VK_QSTATS разное: bcm-vk: добавить обработчик tty irq разное: bcm-vk: добавить интерфейс sysfs разное: bcm-vk: добавить функцию mmap для показа BAR2 MAINTAINERS: bcm-vk: добавить сопровождающего для драйвера Broadcom VK ОБСЛУЖИВАЮЩИЕ | 7 + драйверы/разное/Kconfig | 1 + драйверы/разное/Makefile | 1 + драйверы/разное/bcm-vk/Kconfig | 29+ драйверы/разное/bcm-vk/Makefile | 13 + драйверы/разное/bcm-vk/README | 99++ драйверы/разное/bcm-vk/bcm_vk. h | 510 +++++++++ драйверы/разное/bcm-vk/bcm_vk_dev.c | 1631 ++++++++++++++++++++++++++++++++ драйверы/разное/bcm-vk/bcm_vk_msg.c | 1363 +++++++++++++++++++++++++ драйверы/разное/bcm-vk/bcm_vk_msg.h | 169 +++ драйверы/разное/bcm-vk/bcm_vk_sg.c | 275 +++++ драйверы/разное/bcm-vk/bcm_vk_sg.h | 61++ драйверы/разное/bcm-vk/bcm_vk_sysfs.c | 1411 ++++++++++++++++++++++++++ драйверы/разное/bcm-vk/bcm_vk_tty.c | 336 ++++++ include/uapi/linux/misc/bcm_vk.h | 84++ 15 файлов изменено, 5990 вставок(+) создать режим 100644 drivers/misc/bcm-vk/Kconfig создать режим 100644 drivers/misc/bcm-vk/Makefile создать режим 100644 drivers/misc/bcm-vk/README создать режим 100644 drivers/misc/bcm-vk/bcm_vk.h создать режим 100644 drivers/misc/bcm-vk/bcm_vk_dev.c создать режим 100644 drivers/misc/bcm-vk/bcm_vk_msg.c создать режим 100644 drivers/misc/bcm-vk/bcm_vk_msg.h создать режим 100644 drivers/misc/bcm-vk/bcm_vk_sg.c создать режим 100644 drivers/misc/bcm-vk/bcm_vk_sg.h создать режим 100644 drivers/misc/bcm-vk/bcm_vk_sysfs. c создать режим 100644 drivers/misc/bcm-vk/bcm_vk_tty.c режим создания 100644 include/uapi/linux/misc/bcm_vk.h -- 2.17.1
ПАСЕТО
Пасето — это все, что вам нравится в JOSE (JWT, JWE, JWS) без какого-либо много недостатки дизайна, которые мешают стандартам JOSE.
Реализации PASETO
Поддержка v3/v4
Имя | Язык | Автор | Характеристики | ||||
---|---|---|---|---|---|---|---|
v3.l | v3.p | v4.l | v4.p | преобразовать ключи в/из PASERK | |||
го-пасето | Перейти | Эйдан Т. Вудс | |||||
вк-рв/пвх | Перейти | Олег Вакарев | |||||
нбаарс/paseto4j | Ява | Нанн Баарс | |||||
Давиддесмет/пасето-дотнет | . NET | Дэвид Де Смет | |||||
панва/пасето | Node.js | Филип Скокан | |||||
парагония / пасето | PHP | Инициативные предприятия Paragon | через paserk-php | ||||
пипасето | Питон | Райан Литтлфилд | |||||
питон-пасето | Питон | питон-пасето | |||||
PySETO | Питон | АДЖИТОМИ Дайсуке | |||||
ржавый пасето | Ржавчина | родзилла | |||||
brycx/pasetors | Ржавчина | Йоханнес | |||||
свифт-пасето | Свифт | Эйдан Т. Вудс |
Поддержка v1/v2
Имя | Язык | Автор | Характеристики | |||
---|---|---|---|---|---|---|
в1.л | v1.p | v2.l | v2.p | |||
аутентичное видение/libpaseto | С | Томас Ренот | ||||
Янликларк / Пасето | Эликсир | Ян Кларк | ||||
го-пасето | Перейти | Эйдан Т. Вудс | ||||
библиотека/пасето | Перейти | Шулхан | ||||
вк-рв/пвх | Перейти | Олег Вакарев | ||||
o1egl/пасето | Перейти | Олег Лобанов | ||||
нбаарс/paseto4j | Ява | Нанн Баарс | ||||
атольбро/пасето | Ява | Эндрю Холбрук | ||||
ДжПасето | Ява | Инструментарий Пасето | ||||
Пасето. js | JavaScript | Сэмюэл Джадсон | ||||
Питер-Эванс/пасето-луа | Луа | Питер Эванс | ||||
Скоттбрэди91/IdentityModel | . NET | Скотт Брэди | ||||
Давиддесмет/пасето-дотнет | .NET | Дэвид Де Смет | ||||
dustinsoftware/paseto.net | Стандарт .NET | Дастин Софтвер, Инк. | ||||
панва/пасето | Node.js | Филип Скокан | ||||
парагония / пасето | PHP | Инициативные предприятия Paragon | ||||
пипасето | Питон | Райан Литтлфилд | ||||
питон-пасето | Питон | питон-пасето | ||||
PySETO | Питон | АДЖИТОМИ Дайсуке | ||||
mguymon/paseto. rb | Рубин | Майкл Гаймон Фрэнк Мерфи | ||||
ржавый пасето | Ржавчина | родзилла | ||||
brycx/pasetors | Ржавчина | Йоханнес | ||||
структура/пасето | Ржавчина | Инструктура | ||||
свифт-пасето | Свифт | Эйдан Т. Вудс |
Выступления на конференциях и презентации
- Ни за что, ХОЗЕ! Разработка функций криптографии для простых смертных
- DEFCON 26 — Деревня криптографии и конфиденциальности —
- Слайды (LibreOffice)
- Слайды (PDF) 908:40
- Видео (Ютуб)
Последние три года исследований уязвимостей и криптоанализа не был добр к семейству интернет-стандартов JOSE (большинство широко известный как веб-токены JSON, также известный как JWT).