Как создать детерминированные гиды

107

В нашем приложении мы создаем файлы Xml с атрибутом, имеющим значение Guid. Это значение должно быть согласованным между обновлениями файлов. Таким образом, даже если все остальное в файле изменится, значение guid для атрибута должно остаться прежним.

Одним из очевидных решений было создание статического словаря с именем файла и Guids, которые будут использоваться для них. Затем всякий раз, когда мы генерируем файл, мы ищем в словаре имя файла и используем соответствующий guid. Но это невозможно, потому что мы могли масштабироваться до 100 файлов и не хотели поддерживать большой список руководств.

Таким образом, другой подход заключался в том, чтобы сделать Guid одинаковым на основе пути к файлу. Поскольку наши пути к файлам и структура каталогов приложения уникальны, Guid должен быть уникальным для этого пути. Таким образом, каждый раз, когда мы запускаем обновление, файл получает один и тот же идентификатор в зависимости от его пути. Я нашел один классный способ создания таких « детерминированных направляющих » (спасибо Элтону Стоунману). В основном это делается так:

private Guid GetDeterministicGuid(string input) 

{ 

//use MD5 hash to get a 16-byte hash of the string: 

MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider(); 

byte[] inputBytes = Encoding.Default.GetBytes(input); 

byte[] hashBytes = provider.ComputeHash(inputBytes); 

//generate a guid from the hash: 

Guid hashGuid = new Guid(hashBytes); 

return hashGuid; 

} 

Таким образом, для строки Guid всегда будет одним и тем же.

Есть ли другие подходы или рекомендуемые способы сделать это? Каковы плюсы и минусы этого метода?

Пунит Вора
источник

Ответы:

154

Как упомянуто @bacar, RFC 4122 §4.3 определяет способ создания UUID на основе имени. Преимущество этого (по сравнению с использованием хеша MD5) заключается в том, что они гарантированно не будут конфликтовать с UUID, не основанными на именах, и имеют очень (очень) небольшую вероятность столкновения с другими UUID на основе имен.

В .NET Framework нет встроенной поддержки для их создания, но я разместил на GitHub код , реализующий алгоритм. Его можно использовать следующим образом:

Guid guid = GuidUtility.Create(GuidUtility.UrlNamespace, filePath);

Чтобы еще больше снизить риск конфликтов с другими идентификаторами GUID, вы можете создать частный идентификатор GUID для использования в качестве идентификатора пространства имен (вместо использования идентификатора пространства имен URL, определенного в RFC).

Брэдли Грейнджер
источник
5
@Porges: RFC4122 неверен и содержит опечатки, исправляющие код C ( rfc-editor.org/errata_search.php?rfc=4122&eid=1352 ). Если эта реализация не полностью соответствует RFC4122 и содержащимся в ней исправлениям, пожалуйста, предоставьте дополнительные сведения; Я бы хотел, чтобы он соответствовал стандарту.
Брэдли Грейнджер
1
@BradleyGrainger: Я этого не заметил, спасибо / извините! Я всегда должен не забывать проверять исправления при чтении RFC ... :)
porges
3
@Porges: Пожалуйста / без проблем. Поразительно, что они не обновляют RFC на месте с исправлениями из опечаток. Даже ссылка в конце документа была бы намного полезнее, чем полагаться на то, что читатель вспомнит о поиске ошибок (надеюсь, перед написанием реализации на основе RFC ...).
Брэдли Грейнджер
1
@BradleyGrainger: если вы используете версию HTML, у нее есть ссылка на ошибку из заголовка, например tools.ietf.org/html/rfc4122 . Интересно, есть ли расширение для браузера, чтобы всегда перенаправлять на версию HTML ...
porges
3
Вам следует подумать о том, чтобы внести свой вклад в .NET.
sapphiremirage
29

Это преобразует любую строку в Guid без необходимости импорта внешней сборки.

public static Guid ToGuid(string src)
{
    byte[] stringbytes = Encoding.UTF8.GetBytes(src);
    byte[] hashedBytes = new System.Security.Cryptography
        .SHA1CryptoServiceProvider()
        .ComputeHash(stringbytes);
    Array.Resize(ref hashedBytes, 16);
    return new Guid(hashedBytes);
}

Есть гораздо лучшие способы сгенерировать уникальный Guid, но это способ последовательного обновления строкового ключа данных до ключа данных Guid.

Бен Грипка
источник
Этот фрагмент кода оказался полезным при использовании уникального идентификатора в базе данных для федеративного распространения.
Gleno
6
Предупреждение! Этот код не генерирует действительные Guids / UUID (как также упоминается ниже бакар). Ни версия, ни поле типа не установлены правильно.
MarkusSchaber
3
Разве не было бы столь же эффективным использовать MD5CryptoServiceProvider вместо SHA1, поскольку длина MD5 уже составляет 16 байт?
Brain2000,
21

Как упоминает Роб, ваш метод не генерирует UUID, он генерирует хэш, который выглядит как UUID.

RFC 4122 на UUID , в частности , позволяет детерминированных (имя) на основе UUID , - версии 3 и 5 Использование MD5 и SHA1 (соответственно). Большинство людей, вероятно, знакомы с версией 4, которая случайна. Википедия дает хороший обзор версий. (Обратите внимание, что использование слова «версия» здесь, кажется, описывает «тип» UUID - версия 5 не заменяет версию 4).

Кажется, существует несколько библиотек для генерации UUID версии 3/5, включая модуль uuid python , boost.uuid (C ++) и OSSP UUID . (Я не искал никаких .net)

бакар
источник
1
Это именно то, что нужно для оригинального плаката. У UUID уже есть алгоритм, позволяющий начать со строки и преобразовать ее в GUID. UUID версии 3 хеширует строку с помощью MD5, а версия 5 хеширует ее с помощью SHA1. Важным моментом при создании «guid» является сделать его «уникальным» по сравнению с другими GUID. Алгоритм определяет два бита, которые необходимо установить, а также один полубайт, установленный на 3 или 5, в зависимости от версии 3 или 5.
Ян Бойд,
2
Что касается использования слова «версия», RFC 4122 §4.1.3 гласит: «Версия более точно является подтипом; опять же, мы сохраняем термин для совместимости».
Брэдли Грейнджер,
11
Я разместил код C # для создания GUID v3 и v5 на GitHub: github.com/LogosBible/Logos.Utility/blob/master/src/…
Брэдли Грейнджер,
@BradleyGrainger, я получаю предупреждение Побитовый оператор ИЛИ используется в операнде с расширенным знаком; сначала рассмотрите приведение к меньшему беззнаковому типу
Себастьян
1
Это уже не по теме! Предложите перенести отдельные отчеты об ошибках библиотеки на GitHub.
bacar
3

Вам нужно различать экземпляры класса Guidи идентификаторы, которые уникальны в глобальном масштабе. «Детерминированный гид» на самом деле является хешем (о чем свидетельствует ваш вызов provider.ComputeHash). Хэши имеют гораздо более высокий шанс коллизий (две разные строки создают один и тот же хеш), чем Guid, созданный с помощью Guid.NewGuid.

Таким образом, проблема с вашим подходом заключается в том, что вы должны быть в порядке с возможностью того, что два разных пути будут создавать один и тот же GUID. Если вам нужен идентификатор, уникальный для любой заданной строки пути, то проще всего использовать эту строку . Если вам нужно, чтобы строка была скрыта от пользователей, зашифруйте ее - вы можете использовать ROT13 или что-то более мощное ...

Попытка вставить что-то, что не является чистым GUID, в тип данных GUID может привести к проблемам с обслуживанием в будущем ...

Роб Фонсека-Энсор
источник
2
Вы утверждаете, что «хэши имеют гораздо более высокую вероятность столкновения ... чем Guid, созданный с помощью Guid.NewGuid.». Вы можете подробнее рассказать об этом? С математической точки зрения количество битов, которое можно установить, одинаково, и как MD5, так и SHA1 являются криптографическими хэшами, специально разработанными для снижения вероятности (случайных и преднамеренных) хеш-коллизий.
MarkusSchaber
Я бы сказал, что основное отличие - это криптографическая карта хэшей из одного бесконечного пространства в другое фиксированное пространство с использованием функции. Визуализация хэша, который отображает строки переменной длины в 128 бит, тогда как Guid генерирует псевдослучайные 128 бит. Псевдослучайная генерация не зависит от начального ввода, а, скорее, путем равномерной генерации вывода в пространстве вывода с использованием случайности, засеянной аппаратными средствами или другими способами.
Thai Bui,
1

MD5 слаб, я считаю, что вы можете сделать то же самое с SHA-1 и получить лучшие результаты.

Кстати, просто личное мнение, одевание хэша md5 в качестве GUID не делает его хорошим GUID. GUID по самой своей природе недетерминированы. это похоже на чит. Почему бы просто не назвать вещи своими именами и просто сказать, что это строковый хэш ввода. вы можете сделать это, используя эту строку, а не новую строку guid:

string stringHash = BitConverter.ToString(hashBytes)
Райбер
источник
Спасибо за ваш вклад, но это все еще дает мне строку, и я ищу GUID ...
Пунит Вора,
Хорошо, назовите свой хэш "GUID", проблема решена. Или это реальная проблема , что вы нужны в Guidобъект?
user7116
я бы хотел, чтобы это было так просто .. :) но да, мне нужен объект 'GUID'
Пунит Вора
6
«GUID по самой своей природе недетерминированы» - это верно только для определенных типов («версий») GUID. Однако я согласен с тем, что «одевание хэша md5 в качестве GUID не дает хорошего GUID» по другим причинам, изложенным @Bradley Grainger и @Rob Fonseca-Ensor, и мой ответ на этот вопрос.
бакар