So erstellen Sie deterministische Hilfslinien

103

In unserer Anwendung erstellen wir XML-Dateien mit einem Attribut, das einen Guid-Wert hat. Dieser Wert musste zwischen den Dateiaktualisierungen konsistent sein. Selbst wenn sich alles andere in der Datei ändert, sollte der Guid-Wert für das Attribut gleich bleiben.

Eine naheliegende Lösung bestand darin, ein statisches Wörterbuch mit dem Dateinamen und den für sie zu verwendenden Guids zu erstellen. Wenn wir dann die Datei generieren, suchen wir im Wörterbuch nach dem Dateinamen und verwenden die entsprechende Anleitung. Dies ist jedoch nicht möglich, da wir möglicherweise auf Hunderte von Dateien skalieren und keine große Liste von Anleitungen führen wollten.

Ein anderer Ansatz bestand darin, die Guid basierend auf dem Pfad der Datei gleich zu machen. Da unsere Dateipfade und die Anwendungsverzeichnisstruktur eindeutig sind, sollte die Guid für diesen Pfad eindeutig sein. Jedes Mal, wenn wir ein Upgrade ausführen, erhält die Datei die gleiche Anleitung basierend auf ihrem Pfad. Ich habe einen coolen Weg gefunden, um solche ' Deterministic Guids ' zu generieren (Danke Elton Stoneman). Es macht im Grunde das:

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; 

} 

Wenn Sie also eine Zeichenfolge angeben, ist die Guid immer dieselbe.

Gibt es andere Ansätze oder empfohlene Wege, um dies zu tun? Was sind die Vor- oder Nachteile dieser Methode?

Punit Vora
quelle

Antworten:

151

Wie von @bacar erwähnt, definiert RFC 4122 §4.3 eine Möglichkeit zum Erstellen einer namensbasierten UUID. Dies hat den Vorteil (gegenüber der Verwendung eines MD5-Hashs), dass diese garantiert nicht mit nicht benannten UUIDs kollidieren und eine sehr (sehr) geringe Wahrscheinlichkeit einer Kollision mit anderen namenbasierten UUIDs besteht.

In .NET Framework gibt es keine native Unterstützung für das Erstellen dieser Elemente, aber ich habe Code auf GitHub veröffentlicht , der den Algorithmus implementiert. Es kann wie folgt verwendet werden:

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

Um das Risiko von Kollisionen mit anderen GUIDs noch weiter zu verringern, können Sie eine private GUID erstellen, die als Namespace-ID verwendet wird (anstatt die im RFC definierte URL-Namespace-ID zu verwenden).

Bradley Grainger
quelle
5
@Porges: RFC4122 ist falsch und enthält Errata, die den C-Code korrigieren ( rfc-editor.org/errata_search.php?rfc=4122&eid=1352 ). Wenn diese Implementierung nicht vollständig mit RFC4122 und seinen Errata kompatibel ist, geben Sie bitte weitere Details an. Ich möchte, dass es dem Standard folgt.
Bradley Grainger
1
@BradleyGrainger: Das habe ich nicht bemerkt, danke / sorry! Ich sollte immer daran denken, die Errata zu überprüfen, wenn ich einen RFC lese ... :)
porges
3
@Porges: Gern geschehen / kein Problem. Es verwirrt den Verstand, dass sie den RFC nicht direkt mit den Korrekturen aus den Errata aktualisieren. Selbst ein Link am Ende des Dokuments wäre weitaus hilfreicher, als sich darauf zu verlassen, dass der Leser daran denkt, nach Errata zu suchen (hoffentlich bevor er eine auf dem RFC basierende Implementierung schreibt ...).
Bradley Grainger
1
@BradleyGrainger: Wenn Sie die HTML-Version verwenden, enthält diese einen Link zu den Errata aus dem Header, z . B. tools.ietf.org/html/rfc4122 . Ich frage mich, ob es eine Browser-Erweiterung gibt, um immer zur HTML-Version umzuleiten ...
Porges
2
Sie sollten in Betracht ziehen, dies zum .NET .NET-Repo beizutragen: github.com/dotnet/coreclr/tree/master/src/mscorlib/src/System
sapphiremirage
29

Dadurch wird jede Zeichenfolge in eine Guid konvertiert, ohne dass eine externe Assembly importiert werden muss.

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

Es gibt viel bessere Möglichkeiten, eine eindeutige Guid zu generieren, aber dies ist eine Möglichkeit, einen String-Datenschlüssel konsistent auf einen Guid-Datenschlüssel zu aktualisieren.

Ben Gripka
quelle
Dieses Snippet hat sich als nützlich erwiesen, wenn eine eindeutige Kennung in einer Datenbank für die Verbundverteilung verwendet wird.
Gleno
6
Warnung! Dieser Code generiert keine gültigen Guids / UUIDs (wie auch unten erwähnt). Weder die Version noch das Typfeld sind richtig eingestellt.
MarkusSchaber
3
Wäre es nicht genauso effektiv, den MD5CryptoServiceProvider anstelle des SHA1 zu verwenden, da MD5 bereits 16 Byte lang ist?
Brain2000
20

Wie Rob erwähnt, generiert Ihre Methode keine UUID, sondern einen Hash, der wie eine UUID aussieht.

Der RFC 4122 für UUIDs ermöglicht speziell deterministische (namensbasierte) UUIDs - Die Versionen 3 und 5 verwenden md5 bzw. SHA1. Die meisten Leute kennen wahrscheinlich Version 4, die zufällig ist. Wikipedia gibt einen guten Überblick über die Versionen. (Beachten Sie, dass die Verwendung des Wortes "Version" hier einen "Typ" der UUID zu beschreiben scheint - Version 5 ersetzt Version 4 nicht).

Es scheint einige Bibliotheken zum Generieren von UUIDs der Version 3/5 zu geben, darunter das Python- UUID- Modul , boost.uuid (C ++) und OSSP-UUID . (Ich habe keine .net gesucht)

Speck
quelle
1
Genau danach sucht das Originalplakat. UUID verfügt bereits über einen Algorithmus, mit dem Sie mit einer Zeichenfolge beginnen und diese in eine GUID konvertieren können. UUID Version 3 hascht den String mit MD5, während Version 5 ihn mit SHA1 hascht. Der wichtige Punkt beim Erstellen einer "Guid" besteht darin, sie gegenüber anderen GUIDs "einzigartig" zu machen. Der Algorithmus definiert zwei Bits, die gesetzt werden müssen, und ein Nibble wird entweder auf 3 oder 5 gesetzt, je nachdem, ob es sich um Version 3 oder 5 handelt.
Ian Boyd
2
In Bezug auf die Verwendung des Wortes "Version" heißt es in RFC 4122 §4.1.3: "Die Version ist genauer ein Untertyp; auch hier behalten wir den Begriff für Kompatibilität bei."
Bradley Grainger
11
Ich habe einen C # -Code gepostet, um v3- und v5-GUIDs auf GitHub zu erstellen: github.com/LogosBible/Logos.Utility/blob/master/src/…
Bradley Grainger
@BradleyGrainger, ich erhalte die Warnung Bitwise- oder Operator, der für einen Operanden mit Vorzeichenerweiterung verwendet wird. Ziehen Sie in Betracht, zuerst einen kleineren, nicht signierten Typ zu verwenden
Sebastian,
1
Das kommt vom Thema ab! Schlagen Sie vor, einzelne lib-Fehlerberichte nach GitHub zu verschieben.
Bacar
3

Sie müssen zwischen Instanzen der Klasse Guidund Bezeichnern unterscheiden, die global eindeutig sind. Ein "deterministischer Guid" ist eigentlich ein Hash (wie aus Ihrem Aufruf hervorgeht provider.ComputeHash). Hashes haben eine viel höhere Wahrscheinlichkeit für Kollisionen (zwei verschiedene Strings erzeugen zufällig denselben Hash) als Guid, die über erstellt wurden Guid.NewGuid.

Das Problem bei Ihrem Ansatz ist also, dass Sie mit der Möglichkeit einverstanden sein müssen, dass zwei verschiedene Pfade dieselbe GUID erzeugen. Wenn Sie einen Bezeichner benötigen, der für eine bestimmte Pfadzeichenfolge eindeutig ist, verwenden Sie am einfachsten die Zeichenfolge . Wenn Sie möchten, dass die Zeichenfolge von Ihren Benutzern verdeckt wird, verschlüsseln Sie sie - Sie können ROT13 oder etwas Stärkeres verwenden ...

Der Versuch, etwas, das keine reine GUID ist, in den GUID-Datentyp einzuschleusen, könnte in Zukunft zu Wartungsproblemen führen ...

Rob Fonseca-Ensor
quelle
2
Sie behaupten, "Hashes haben eine viel höhere Kollisionswahrscheinlichkeit ... als Guid, der über Guid.NewGuid erstellt wurde." Können Sie das näher erläutern? Aus mathematischer Sicht ist die Anzahl der Bits, die gesetzt werden können, gleich, und sowohl MD5 als auch SHA1 sind kryptografische Hashes, die speziell entwickelt wurden, um die Wahrscheinlichkeit von (zufälligen und absichtlichen) Hash-Kollisionen zu verringern.
MarkusSchaber
Ich würde sagen, der Hauptunterschied besteht darin, dass kryptografische Hashes mithilfe einer Funktion von einem unendlichen Raum in einen anderen festen Raum abgebildet werden. Abbildung eines Hashs, der Zeichenfolgen variabler Länge auf 128 Bit abbildet, während Guid pseudozufällige 128 Bit generiert. Die Pseudozufallsgenerierung beruht nicht auf einer anfänglichen Eingabe, sondern durch gleichmäßiges Generieren der Ausgabe im Ausgaberaum unter Verwendung von Zufälligkeit, die von der Hardware oder anderen Mitteln ausgesät wird.
Thai Bui
2

MD5 ist schwach. Ich glaube, Sie können mit SHA-1 dasselbe tun und bessere Ergebnisse erzielen.

Übrigens, nur eine persönliche Meinung: Wenn Sie einen MD5-Hash als GUID verkleiden, ist dies keine gute GUID. GUIDs sind von Natur aus nicht deterministisch. Das fühlt sich an wie ein Betrüger. Warum nennst du nicht einfach einen Spaten einen Spaten und sagst, es ist ein String, der als Hash der Eingabe gerendert wird? Sie können dies tun, indem Sie diese Zeile anstelle der neuen Hilfslinie verwenden:

string stringHash = BitConverter.ToString(hashBytes)
Ryber
quelle
Vielen Dank für Ihre Eingabe, aber dies gibt mir immer noch eine Zeichenfolge, und ich suche eine GUID ...
Punit Vora
Ok, nenne deinen Hash eine "GUID", Problem gelöst. Oder ist das eigentliche Problem, dass Sie ein Objekt benötigenGuid ?
user7116
Ich wünschte, es wäre so einfach .. :) aber ja, ich brauche ein 'GUID'-Objekt
Punit Vora
5
"GUIDs sind von Natur aus nicht deterministisch" - dies gilt nur für bestimmte Arten ('Versionen') von GUIDs. Ich stimme jedoch zu, dass "das Ankleiden eines MD5-Hash als GUID keine gute GUID ergibt" aus anderen Gründen, wie von @Bradley Grainger und @Rob Fonseca-Ensor dargelegt, und meiner Antwort auf diese Frage.
Bacar