Come creare guide deterministiche


103

Nella nostra applicazione stiamo creando file Xml con un attributo che ha un valore Guid. Questo valore doveva essere coerente tra gli aggiornamenti dei file. Quindi, anche se tutto il resto nel file cambia, il valore guid per l'attributo dovrebbe rimanere lo stesso.

Una soluzione ovvia è stata creare un dizionario statico con il nome del file e le guide da utilizzare per loro. Quindi ogni volta che generiamo il file, cerchiamo nel dizionario il nome del file e utilizziamo il guid corrispondente. Ma questo non è fattibile perché potremmo scalare fino a centinaia di file e non volevamo mantenere un lungo elenco di guide.

Quindi un altro approccio è stato quello di rendere il Guid lo stesso in base al percorso del file. Poiché i nostri percorsi dei file e la struttura della directory dell'applicazione sono univoci, il Guid dovrebbe essere univoco per quel percorso. Quindi ogni volta che eseguiamo un aggiornamento, il file ottiene lo stesso guid in base al suo percorso. Ho trovato un modo fantastico per generare tali " Guide deterministiche " (Grazie Elton Stoneman). Fondamentalmente fa questo:

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; 

} 

Quindi data una stringa, il Guid sarà sempre lo stesso.

Esistono altri approcci o modi consigliati per farlo? Quali sono i pro o i contro di questo metodo?

Risposte:


151

Come menzionato da @bacar, RFC 4122 §4.3 definisce un modo per creare un UUID basato sul nome. Il vantaggio di fare questo (rispetto al solo utilizzo di un hash MD5) è che è garantito che questi non entrino in collisione con UUID non basati su nome e hanno una (molto) piccola possibilità di collisione con altri UUID basati su nome.

Non c'è supporto nativo in .NET Framework per la creazione di questi, ma ho pubblicato del codice su GitHub che implementa l'algoritmo. Può essere utilizzato come segue:

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

Per ridurre ulteriormente il rischio di collisioni con altri GUID, è possibile creare un GUID privato da utilizzare come ID spazio dei nomi (invece di utilizzare l'ID spazio dei nomi URL definito nella RFC).


5
@Porges: RFC4122 non è corretto e contiene errori che correggono il codice C ( rfc-editor.org/errata_search.php?rfc=4122&eid=1352 ). Se questa implementazione non è completamente conforme a RFC4122 e ai relativi errata, fornire ulteriori dettagli; Vorrei che seguisse lo standard.
Bradley Grainger

1
@ BradleyGrainger: non me ne sono accorto, grazie / scusa! Dovrei sempre ricordarmi di controllare l'errata quando leggo un RFC ... :)
porges

3
@Porges: Prego / nessun problema. Sconvolge la mente che non aggiornino l'RFC sul posto con le correzioni dell'errata. Anche un collegamento alla fine del documento sarebbe di gran lunga più utile che affidarsi al lettore per ricordarsi di cercare gli errata (si spera prima di scrivere un'implementazione basata sull'RFC ...).
Bradley Grainger

1
@BradleyGrainger: se usi la versione HTML ha un link all'errata dall'intestazione , es . Tools.ietf.org/html/rfc4122 . Mi chiedo se ci sia un'estensione del browser per reindirizzare sempre alla versione HTML ...
porges

2
Dovresti considerare di contribuire al repository .NET .NET è qui: github.com/dotnet/coreclr/tree/master/src/mscorlib/src/System
sapphiremirage

29

Questo convertirà qualsiasi stringa in un Guid senza dover importare un assembly esterno.

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

Esistono modi molto migliori per generare un Guid univoco, ma questo è un modo per aggiornare in modo coerente una chiave dati stringa a una chiave dati Guid.


Ho trovato questo frammento utile quando si utilizza un identificatore univoco in un database per la distribuzione federata.
Gleno

6
Avvertimento! Questo codice non genera GUID / UUID validi (come anche menzionato di seguito). Né la versione né il campo del tipo sono impostati correttamente.
MarkusSchaber

3
Non sarebbe altrettanto efficace utilizzare MD5CryptoServiceProvider invece di SHA1, poiché MD5 ha già una lunghezza di 16 byte?
Brain2000

20

Come menziona Rob, il tuo metodo non genera un UUID, genera un hash che assomiglia a un UUID.

L' RFC 4122 sugli UUID consente specificamente UUID deterministici (basati sul nome) - Le versioni 3 e 5 utilizzano md5 e SHA1 (rispettivamente). La maggior parte delle persone ha probabilmente familiarità con la versione 4, che è casuale. Wikipedia offre una buona panoramica delle versioni. (Si noti che l'uso della parola "versione" qui sembra descrivere un "tipo" di UUID - la versione 5 non sostituisce la versione 4).

Sembra che ci siano alcune librerie là fuori per la generazione di UUID versione 3/5, incluso il modulo uuid python , boost.uuid (C ++) e OSSP UUID . (Non ho cercato nessuno .net)


1
Questo è esattamente ciò che cerca il poster originale. UUID ha già un algoritmo per iniziare con una stringa e convertirla in un GUID. La versione 3 dell'UUID hash la stringa con MD5, mentre la versione 5 l'ha hash con SHA1. Il punto importante nella creazione di un "guid" è renderlo "unico" rispetto ad altri GUID. L'algoritmo definisce due bit che devono essere impostati, così come un nibble è impostato su 3 o 5, a seconda che sia la versione 3 o 5.
Ian Boyd

2
Per quanto riguarda l'uso della parola "versione", RFC 4122 §4.1.3 afferma: "La versione è più accuratamente un sottotipo; ancora una volta, conserviamo il termine per compatibilità."
Bradley Grainger

11
Ho pubblicato del codice C # per creare GUID v3 e v5 su GitHub: github.com/LogosBible/Logos.Utility/blob/master/src/…
Bradley Grainger

@ BradleyGrainger, ricevo un avviso Bitwise o operatore utilizzato su un operando con segno esteso; considera prima di lanciare un tipo non firmato più piccolo
Sebastian

1
Questo sta diventando fuori tema! Suggerire di spostare le singole segnalazioni di bug della libreria su GitHub.
bacar

3

È necessario fare una distinzione tra istanze della classe Guide identificatori che sono globalmente univoci. Un "guid deterministico" è in realtà un hash (come evidenziato dalla tua chiamata a provider.ComputeHash). Gli hash hanno una probabilità molto maggiore di collisioni (due stringhe diverse che producono lo stesso hash) rispetto a Guid creato tramite Guid.NewGuid.

Quindi il problema con il tuo approccio è che dovrai accettare la possibilità che due percorsi diversi producano lo stesso GUID. Se hai bisogno di un identificatore univoco per una determinata stringa di percorso, la cosa più semplice da fare è semplicemente usare la stringa . Se hai bisogno che la stringa venga oscurata dai tuoi utenti, crittografala : puoi usare ROT13 o qualcosa di più potente ...

Il tentativo di inserire qualcosa che non è un GUID puro nel tipo di dati GUID potrebbe portare a problemi di manutenzione in futuro ...


2
Affermi che "gli hash hanno una probabilità molto maggiore di collisioni ... rispetto a Guid creato tramite Guid.NewGuid". Puoi approfondire questo? Da un punto di vista matematico, il numero di bit che è possibile impostare è lo stesso e sia MD5 che SHA1 sono hash crittografici, progettati specificatamente per ridurre la probabilità di collisioni di hash (accidentali e intenzionali).
MarkusSchaber

Direi che la differenza principale è la mappa degli hash crittografici da uno spazio infinito a un altro spazio fisso utilizzando una funzione. Creazione di immagini di un hash che mappa stringhe di lunghezza variabile a 128 bit mentre Guid genera 128 bit pseudo-casuali. La generazione pseudo-casuale non si basa su un input iniziale ma piuttosto generando l'output in modo uniforme nello spazio di output utilizzando la casualità seminata dall'hardware o altri mezzi.
Thai Bui

2

MD5 è debole, credo che tu possa fare la stessa cosa con SHA-1 e ottenere risultati migliori.

A proposito, solo un'opinione personale, vestire un hash md5 come GUID non lo rende un buon GUID. I GUID per loro stessa natura non sono deterministici. questo sembra un imbroglio. Perché non chiamare semplicemente un picche un picche e dire semplicemente che è una stringa resa hash dell'input. puoi farlo usando questa linea, piuttosto che la nuova linea guida:

string stringHash = BitConverter.ToString(hashBytes)

Grazie per il tuo contributo, ma questo mi dà ancora una stringa, e sto cercando un GUID ...
Punit Vora

Ok, chiama il tuo hash "GUID", problema risolto. O il vero problema è che hai bisogno di un Guidoggetto?
user7116

vorrei che fosse così semplice .. :) ma sì, ho bisogno di un oggetto "GUID"
Punit Vora

5
"I GUID per loro stessa natura non sono deterministici" - questo è vero solo per alcuni tipi ('versioni') di GUID. Tuttavia sono d'accordo sul fatto che "vestire un hash md5 come GUID non è un buon GUID" per altri motivi, come spiegato da @Bradley Grainger e @Rob Fonseca-Ensor, e la mia risposta a questa domanda.
Bacar
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.