反向工程String.GetHashCode

4

String.GetHashCode的行为取决于程序结构。因此,它将在x86上返回一个值,在x64上返回另一个值。我有一个测试应用程序,必须在x86上运行,并且必须预测要在x64上运行的应用程序的哈希代码输出。

以下是mscorwks中String.GetHashCode实现的反汇编代码。

public override unsafe int GetHashCode()
{
      fixed (char* text1 = ((char*) this))
      {
            char* chPtr1 = text1;
            int num1 = 0x15051505;
            int num2 = num1;
            int* numPtr1 = (int*) chPtr1;
            for (int num3 = this.Length; num3 > 0; num3 -= 4)
            {
                  num1 = (((num1 << 5) + num1) + (num1 >≫ 0x1b)) ^ numPtr1[0];
                  if (num3 <= 2)
                  {
                        break;
                  }
                  num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr1[1];
                  numPtr1 += 2;
            }
            return (num1 + (num2 * 0x5d588b65));
      }
}

有人能将这个函数移植到一个安全的实现吗?

1
您可以使用索引器访问字符串中的字符。 - Christopher Currens
15
为什么需要让哈希码匹配?不要为任何目的存储哈希码,因为实现随时可能会更改!请参阅Eric Lippert的博客中的“准则和规则 - GetHashCode”,其中指出使用GetHashCode的消费者不能保证其稳定性,无论是随时间还是跨应用程序域。此外,还有一段有关“安全问题:不要‘非标签’使用GetHashCode”的内容。 - user47589
请参见此问题 - user47589
我没有意识到GetHashCode如此灵活。所以是的 - 愚人的任务。我同意。很好的建议。我将追求使用可重复且与架构无关的哈希算法。 - Kenn
肯恩:作为以后的参考,你可能会发现参考源很有趣。为什么要反汇编.Net Framework代码,当你可以查看实际的、完全注释的源代码呢? - Brian
对于那些陷入了(或者是别人的)令人后悔的过去决定依赖这个哈希算法的人,或者如果你只想要一个简单可重复的字符串哈希,下面有一个安全的实现。 - reads0520
4个回答

20

哈希码并不打算在不同平台上或甚至在同一系统上的多次运行中具有可重复性。 你正在走错路。如果你不改变方向,你的道路将会困难,有朝一日可能以泪水收场。

您想要解决的真正问题是什么?是否可以编写自己的哈希函数,作为扩展方法或包装类的 GetHashCode 实现,并使用该函数替代原有的哈希函数?


只有“可能”会以泪水收场?! - bacar
1
@bacar:尽管你违反了规则,但一切可能都运行正常。然而,某一天发生了一些事情(.NET更新?服务器迁移?任何事情都有可能),你的应用程序突然崩溃,没有任何警告。 - Jon
我只是比你更确信它最终会以泪水结束 :-) - bacar

16

首先,Jon是正确的;这是一项愚蠢的任务。我们用来“吃自己的狗粮”的框架的内部调试版本每天都会更改哈希算法,以防止人们构建依赖于不可靠实现细节(这些细节被记录为随时可能更改)的系统,即使是测试系统。

与其将一个被记录为不适合模拟的系统的仿真形式奉为圭臬,我的建议是退一步,问问自己为什么要做这种危险的事情,这是否真的是一个必要条件?

其次,StackOverflow是一个技术问答网站,而不是“免费替我完成工作”的网站。如果你一心想要做这件危险的事情,并且需要有人可以将不安全的代码重写为等效的安全代码,那么我建议你雇用一个能够为你完成这项工作的人


1
哦天啊!埃里克,你真的很生气 :) - Konstantin
我没意识到 GetHashCode 如此灵活。所以,是的 - 这是一个傻瓜行为。我同意,这是个好建议。我将追求使用可重复且与架构无关的哈希算法。 - Kenn
4
@Konstantin: 我一点也不生气;作为他的客户,我希望肯能够成功。因此,我坚定地声明:(1)这是一个不好的想法,可能会导致未来高昂的成本,(2)StackOverflow是一个寻求“事实”的好地方,但却是一个寻求“免费工作”的坏地方。知道这一点的人更有可能成功地使用StackOverflow。 - Eric Lippert
1
@configurator:我表达不清楚。算法每天都会变化,因为算法的内部版本考虑了当前每天都在变化的构建编号。 - Eric Lippert
1
阅读String.cs的源代码让我感到愉悦。//...那些是你代码中的漏洞。\n hash1 ^= ThisAssembly.DailyBuildNumber; - Brian
显示剩余5条评论

5

虽然这里提供的所有警告都是有效的,但它们并没有回答问题。我遇到了这样一种情况:在生产中,GetHashCode()已经被用于一个持久化值,而我别无选择,只能重新使用默认的.NET 2.0 32位x86(小端)算法进行实现。我按照下面所示重新编码,没有使用不安全操作,看起来这个方法是可行的。希望这能帮助到某些人。

// The GetStringHashCode() extension method is equivalent to the Microsoft .NET Framework 2.0
// String.GetHashCode() method executed on 32 bit systems.
public static int GetStringHashCode(this string value)
{
    int hash1 = (5381 << 16) + 5381;
    int hash2 = hash1;

    int len = value.Length;
    int intval;
    int c0, c1;
    int i = 0;
    while (len > 0)
    {
        c0 = (int)value[i];
        c1 = len > 1 ? (int)value[i + 1] : 0;
        intval = c0 | (c1 << 16);
        hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ intval;
        if (len <= 2)
        {
            break;
        }
        i += 2;
        c0 = (int)value[i];
        c1 = len > 3 ? (int)value[i + 1] : 0;
        intval = c0 | (c1 << 16);
        hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ intval;
        len -= 4;
        i += 2;
    }

    return hash1 + (hash2 * 1566083941);
}

谢谢您的回复。毋庸置疑,哈希码不应被依赖,但回答这个问题仍然有所帮助。在我的情况下,我使用哈希码来解析预定义颜色调色板的索引以呈现日志。这段代码有助于将其移植到JavaScript。我需要的特定功能是相似的输入字符串生成截然不同的整数。 - Daniel

0
以下内容完全复制了在.NET 4.7(或更早版本)上默认String哈希码的结果。这是由以下代码生成的哈希码:
  • String实例上执行默认操作:"abc".GetHashCode()
  • StringComparer.Ordinal.GetHashCode("abc")
  • 各种获取StringComparison.Ordinal枚举的String方法。
  • System.Globalization.CompareInfo.GetStringComparer(CompareOptions.Ordinal)
在具有完整JIT优化的发布版本上测试,这些版本比内置的.NET代码表现稍好,并且已经过大量单元测试,确保与.NET行为完全一致。请注意,x86x64有不同的版本。您的程序通常应包括两者;下面是一个调用挂件,它在运行时选择适当的版本。

x86   -   (.NET在32位模式下运行)

static unsafe int GetHashCode_x86_NET(int* p, int c)
{
    int h1, h2 = h1 = 0x15051505;

    while (c > 2)
    {
        h1 = ((h1 << 5) + h1 + (h1 >> 27)) ^ *p++;
        h2 = ((h2 << 5) + h2 + (h2 >> 27)) ^ *p++;
        c -= 4;
    }

    if (c > 0)
        h1 = ((h1 << 5) + h1 + (h1 >> 27)) ^ *p++;

    return h1 + (h2 * 0x5d588b65);
}

x64   -   (.NET以64位模式运行)

static unsafe int GetHashCode_x64_NET(Char* p)
{
    int h1, h2 = h1 = 5381;

    while (*p != 0)
    {
        h1 = ((h1 << 5) + h1) ^ *p++;

        if (*p == 0)
            break;

        h2 = ((h2 << 5) + h2) ^ *p++;
    }
    return h1 + (h2 * 0x5d588b65);
}

调用适用于任何平台(x86/x64)的测试工具/扩展方法:

readonly static int _hash_sz = IntPtr.Size == 4 ? 0x2d2816fe : 0x162a16fe;

public static unsafe int GetStringHashCode(this String s)
{
    /// Note: x64 string hash ignores remainder after embedded '\0'char (unlike x86)
    if (s.Length == 0 || (IntPtr.Size == 8 && s[0] == '\0'))
        return _hash_sz;

    fixed (char* p = s)
        return IntPtr.Size == 4 ?
            GetHashCode_x86_NET((int*)p, s.Length) :
            GetHashCode_x64_NET(p);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接