如何在.net(c#)中为可安全存储在数据库中的字符串创建HashCode?

Ian*_*ose 44 .net c# database hashcode gethashcode

引用 Eric Lippert的GetHashCode指南和规则:

规则:GetHashCode的消费者不能依赖它随着时间的推移或跨appdomains的稳定性

假设您有一个Customer对象,其中包含一系列字段,如Name,Address等.如果在两个不同的进程中使用完全相同的数据生成两个这样的对象,则它们不必返回相同的哈希代码.如果你在星期二的一个进程中创建这样一个对象,关闭它,并在星期三再次运行程序,哈希码可能会有所不同.

这在过去曾经被人咬伤过.System.String.GetHashCode的文档特别指出,两个相同的字符串在CLR的不同版本中可以具有不同的哈希码,实际上它们也是如此.不要在数据库中存储字符串哈希并期望它们永远是相同的,因为它们不会.

那么创建一个可以存储在数据库中的字符串的HashCode的正确方法是什么?

(请告诉我,我不是第一个在我写的软件中留下这个错误的人!)

Jon*_*eet 71

它取决于您希望哈希具有哪些属性.例如,你可以写这样的东西:

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

    unchecked
    {
        int hash = 23;
        foreach (char c in text)
        {
            hash = hash * 31 + c;
        }
        return hash;
    }
}
Run Code Online (Sandbox Code Playgroud)

只要您记录这是计算哈希的方式,那就是有效的.它绝不是加密安全或类似的东西,但你可以毫无问题地坚持它.在序数意义上绝对相等的两个字符串(即没有应用文化相等等,完全逐字符相同)将使用此代码生成相同的散列.

当您依赖于未记录的散列时会出现问题- 即某些东西遵循GetHashCode()但不能保证在不同版本之间保持相同......就像string.GetHashCode().

像这样编写和记录你自己的哈希有点像说,"这个敏感的信息是用MD5(或其他)散列的".只要它是一个定义明确的哈希,那很好.

编辑:其他答案建议使用加密哈希,如SHA-1或MD5.我要说的是,在我们知道需要加密安全性而不仅仅是稳定性之前,没有必要经历将字符串转换为字节数组并对其进行散列的严格要求.当然,如果哈希值指用于任何与安全有关的,行业标准的哈希正是你应该达到什么样的.但在问题的任何地方都没有提到.

  • 整齐!从[Effective Java(2008),第48页](https://books.google.com/books?id=ka2VUBqHiWkC):*选择值31是因为它是一个奇数素数.如果它是偶数并且乘法溢出,则信息将丢失,因为乘法相当于移位.使用素数的优势不太明显,但它是传统的.31的一个很好的属性是乘法可以用移位和减法代替以获得更好的性能:`31*i ==(i << 5) - i`.现代VM自动进行这种优化.*看起来像一些有趣的阅读; 再次感谢. (17认同)
  • @ruffin:他们是Josh Bloch推荐的价值观.乘以31是有效的,因为它可以作为移位和减法来完成.还有其他各种问题在谈论这个问题 - 说实话,这是一种黑暗的艺术. (12认同)
  • 关于23和`*31`有什么魔力吗?相反,任何理由选择那些超过任何其他价值观?...超过任何其他[记录]散列方法?我猜不是,虽然31比ASCII表格少一个让我不必要地怀疑. (4认同)
  • “现代虚拟机自动进行这种优化”这也适用于 .NET 吗? (2认同)

Sco*_*ain 8

以下是.NET计算64位系统的字符串哈希码的当前方式的重新实现.这不会像真实GetHashCode()那样使用指针,因此它会稍微慢一点,但它确实使内部更改更具弹性string,这将提供比Jon Skeet版本更均匀分布的哈希代码,这可能会导致更好的词典查找时间.

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)