2011-03-01 10 views
38

Cytując Guidelines and rules for GetHashCode Eric Lippert:Jak utworzyć kod HashCode w .net (C#) dla ciągu, który można bezpiecznie przechowywać w bazie danych?

Reguła: Konsumenci GetHashCode nie może powoływać się na to jest stabilne w czasie lub po drugiej AppDomains

Załóżmy, że masz obiekt klienta że ma kilka pola takie jak Name, Address, i tak dalej. Jeśli wykonasz dwa takie obiekty z dokładnie tymi samymi danymi w dwóch różnych procesach, to one nie muszą zwracać tego samego kodu hash . Jeśli taki obiekt zostanie utworzony we wtorek w jednym procesie, zamknij go, i ponownie uruchom program na . Środa, kody skrótu mogą być różne od .

To ukąsiło ludzi w przeszłości. Dokumentacja System.String.GetHashCode zauważa konkretnie, że dwa identyczne łańcuchy mogą mieć różne kody hash w różnych wersjach CLR i w rzeczywistości robią. Nie przechowuj skrótów łańcuchów w bazach danych i spodziewaj się, że będą one zawsze takie same, ponieważ nie będą.

Jaki jest poprawny sposób utworzenia HashCode ciąg, który można przechowywać w bazie danych?

(proszę mi powiedzieć, że nie jestem pierwszą osobą, która opuścił ten błąd w oprogramowaniu napisałem!)

+2

Cóż, nigdy nie polegam na GetHashCode, ponieważ wiem, jak niedbale wdrażam tę metodę. Sądzę, że inni nie robią tego lepiej ... ;-) –

+3

Nie jesteś pierwszą osobą, która opuściła ten błąd w oprogramowaniu, które napisałeś. – Bobby

+2

Silniki Dbase są już bardzo dobre w hashowaniu. Po prostu utwórz indeks dla kolumny. –

Odpowiedz

64

Zależy od tego, jakie właściwości ma mieć skrót mieszania. Na przykład, mógłby prostu napisać coś takiego:

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

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

Dopóki dokument, że to, w jaki sposób jest obliczana hash, to ważne. Nie jest to w żaden sposób kryptograficznie bezpieczne ani nic w tym stylu, ale można je utrzymywać bez żadnych problemów. Dwa ciągi, które są absolutnie równe w sensie porządkowym (tj. Bez równości kulturowej itp. Zastosowane, dokładnie taki sam znak po znaku), wytworzą ten sam skrót z tym kodem.

Problemy przyjść kiedy polegać na nieudokumentowane mieszaja - to jest coś, co posłuszny GetHashCode() ale w gwarantowanym pozostać taka sama z wersji do wersji ... jak string.GetHashCode() żaden sposób.

Pisanie i dokumentowanie własnego skrótu przypomina to: "Ta poufna informacja jest zaszyfrowana przy pomocy MD5 (lub czegoś podobnego)". Dopóki jest to dobrze zdefiniowany skrót, jest w porządku.

EDYCJA: Inne odpowiedzi sugerują używanie skrótów kryptograficznych, takich jak SHA-1 lub MD5.Powiedziałbym, że dopóki nie będziemy wiedzieć, że istnieje potrzeba bezpieczeństwa kryptograficznego, a nie tylko stabilność, nie ma sensu przechodzić przez rigmarole konwersji łańcucha na tablicę bajtów i mieszania tego. Oczywiście, jeśli hash ma być przeznaczonym do użycia w celach związanych z bezpieczeństwem, standardowym hashem jest dokładnie, do czego powinieneś sięgać. Ale nie zostało to nigdzie wspomniane.

+3

Czy jest coś magicznego o 23 i '* 31'? Przeciwnie, jakikolwiek powód, aby wybierać te ponad wszelkie inne wartości? ... nad jakąkolwiek [udokumentowaną] metodą haszującą? Zgaduję, że nie, chociaż 31 jest o jeden mniej niż w przypadku drukarek ASCII, co niepotrzebnie mnie niepokoi. – ruffin

+10

@ruffin: Są to wartości zalecane przez Josha Blocha. Mnożenie przez 31 jest efektywne, ponieważ może być wykonane jako przesunięcie i odjęcie. Mówi się o różnych innych kwestiach - to szczera sztuka. –

+15

Schludny! Z [Effective Java (2008), strona 48] (https://books.google.com/books?id=ka2VUBqHiWkC): * Wybrano wartość 31, ponieważ jest ona nieparzysta. Jeśli byłby równy i mnożenie się przepełniło, informacja byłaby stracona, ponieważ mnożenie jest równoznaczne z przesunięciem. Zaleta korzystania z liczby pierwszej jest mniej wyraźna, ale jest tradycyjna. Miła właściwość 31 polega na tym, że mnożenie może zostać zastąpione przesunięciem i odejmowaniem dla lepszej wydajności: '31 * i == (i << 5) - i'. Współczesne maszyny wirtualne automatycznie wykonują tego rodzaju optymalizację. * Wygląda na zabawne czytanie; dzięki jeszcze raz. – ruffin

1

Odpowiedź jest tylko napisać własną funkcję mieszającą. Możesz znaleźć źródło dla niektórych, klikając poniższe linki w komentarzach do opublikowanego artykułu. Możesz też użyć wbudowanej funkcji haszowania, która pierwotnie była przeznaczona do kryptografii (MD5, SHA1 itd.) I po prostu nie używać wszystkich bitów.

6

Oto reimplementacja the current way .NET calculates it's string hash code for 64 bit systems. To nie używa wskaźników takich jak prawdziwe GetHashCode(), więc będzie nieco wolniej, ale spowoduje, że będzie bardziej odporny na zmiany wewnętrzne na string, da to bardziej równomiernie rozproszony kod skrótu niż Jon Skeet's version, co może skutkować lepszymi czasami wyszukiwania w słownikach .

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); 
     } 
    } 
} 
Powiązane problemy