2011-03-01 10 views
38

Per citare Guidelines and rules for GetHashCode di Eric Lippert:Come posso creare un codice hash in .net (C#) per una stringa che è sicura da memorizzare in un database?

Regola: I consumatori di GetHashCode non possono contare su di esso che è stabile nel tempo o AppDomain attraverso

Supponiamo di avere un oggetto Customer che ha un sacco di campi come Nome, Indirizzo e così via. Se si effettuano due oggetti simili con lo nello stesso dati in due diversi processi, essi non devono restituire lo stesso codice hash allo stesso . Se effettui un tale oggetto su martedì in un unico processo, spegnilo, ed esegui di nuovo il programma su Mercoledì, i codici hash possono essere diversi.

Questo ha morso persone in passato. La documentazione per System.String.GetHashCode osserva espressamente che due identici stringhe possono avere diversi codici hash nelle diverse versioni del CLR, e in effetti lo fanno. Non archiviare gli hash delle stringhe nei database e aspettarsi che siano sempre gli stessi, perché non lo saranno.

Quindi qual è il modo corretto di creare un codice hash di una stringa che posso memorizzare in un database?

(Ti prego, dimmi che non sono la prima persona ad aver lasciato questo bug nel software che ho scritto!)

+2

Bene, non mi fido mai di GetHashCode, perché so, quanto sia sciatta l'implementazione di questo metodo. Credo che gli altri non lo stiano facendo meglio ... ;-) –

+3

Non sei la prima persona che ha lasciato questo bug nel software che hai scritto. – Bobby

+2

I motori Dbase sono già molto bravi con le stringhe di hashing. Basta creare un indice per la colonna. –

risposta

64

Dipende dalle proprietà che si desidera avere. Ad esempio, si potrebbe solo scrivere qualcosa del genere:

public int HashString(string text) 
{ 
    // TODO: Determine nullity policy. 

    unchecked 
    { 
     int hash = 23; 
     foreach (char c in text) 
     { 
      hash = hash * 31 + c; 
     } 
     return hash; 
    } 
} 

Fino a quando si documento che è così che l'hash viene calcolato, questo è valido. Non è in alcun modo crittograficamente sicuro o qualcosa del genere, ma puoi persistere senza problemi. Due stringhe che sono assolutamente uguali in senso ordinale (cioè senza uguaglianza culturale applicata, esattamente carattere per carattere lo stesso) produrranno lo stesso hash con questo codice.

I problemi arrivano quando si basano su privi di documenti hashing - vale a dire una cosa che obbedisce GetHashCode(), ma non è in alcun modo garantito per rimanere lo stesso da una versione all'altra ... come string.GetHashCode().

Scrivere e documentare il proprio hash come questo è un po 'come dire "Questa informazione sensibile è sottoposta a hashing con MD5 (o qualsiasi altra cosa)". Finché si tratta di un hash ben definito, va bene.

MODIFICA: Altre risposte hanno suggerito l'uso di hash crittografici come SHA-1 o MD5.Direi che fino a quando non ci sarà un requisito per la sicurezza crittografica piuttosto che una semplice stabilità, non ha senso passare attraverso la trappola della conversione della stringa in un array di byte e l'hashing. Naturalmente se l'hash è destinato a essere utilizzato per qualsiasi problema di sicurezza, un hash standard del settore è esattamente per cosa si dovrebbe raggiungere. Ma questo non è stato menzionato da nessuna parte nella domanda.

+3

C'è qualcosa di magico su 23 e '* 31'? Piuttosto, c'è qualche ragione per scegliere quelli oltre qualsiasi altro valore? ... su qualsiasi altro metodo di hashing [documentato]? Sto indovinando no, anche se 31 essere uno in meno di stampatori ASCII mi ha tenuto sospettosamente inutilmente. – ruffin

+10

@ruffin: sono valori raccomandati da Josh Bloch. Moltiplicare per 31 è efficiente perché può essere fatto come uno spostamento e una sottrazione. Ci sono varie altre domande che parlano di questo: è un po 'un'arte oscura, per essere onesti. –

+15

Neat! Da [Effective Java (2008), pagina 48] (https://books.google.com/books?id=ka2VUBqHiWkC): * Il valore 31 è stato scelto perché è un numero primo dispari. Se fosse pari e la moltiplicazione traboccasse, le informazioni andrebbero perse, poiché la moltiplicazione equivale al cambiamento. Il vantaggio di utilizzare un primo è meno chiaro, ma è tradizionale. Una bella proprietà di 31 è che la moltiplicazione può essere sostituita da uno spostamento e una sottrazione per prestazioni migliori: '31 * i == (i << 5) - i'. Le moderne macchine virtuali eseguono automaticamente questo tipo di ottimizzazione. * Sembra una lettura divertente; grazie ancora. – ruffin

1

La risposta è quello di scrivere solo la propria funzione di hashing. Puoi trovare la fonte per alcuni seguendo i link nei commenti all'articolo che hai postato. Oppure puoi usare una funzione di hash incorporata originariamente pensata per la crittografia (MD5, SHA1, ecc.) E non usare tutti i bit.

6

Ecco una reimplementazione di the current way .NET calculates it's string hash code for 64 bit systems. Questo non usa puntatori come il vero GetHashCode() così sarà leggermente più lento, ma lo renderà più resistente alle modifiche interne a string, questo darà un codice hash più uniformemente distribuito rispetto a Jon Skeet's version che potrebbe risultare in tempi di ricerca migliori nei dizionari .

public static class StringExtensionMethods 
{ 
    public static int GetStableHashCode(this string str) 
    { 
     unchecked 
     { 
      int hash1 = 5381; 
      int hash2 = hash1; 

      for(int i = 0; i < str.Length && str[i] != '\0'; i += 2) 
      { 
       hash1 = ((hash1 << 5) + hash1)^str[i]; 
       if (i == str.Length - 1 || str[i+1] == '\0') 
        break; 
       hash2 = ((hash2 << 5) + hash2)^str[i+1]; 
      } 

      return hash1 + (hash2*1566083941); 
     } 
    } 
} 
Problemi correlati