Обратный инжиниринг String.GetHashCode

Поведение String.GetHashCode зависит от архитектуры программы. Таким образом, он вернет одно значение в x86 и одно значение в x64. У меня есть тестовое приложение, которое должно работать в x86, и оно должно предсказать вывод хеш-кода из приложения, которое должно работать в x64.

Ниже приведена разборка реализации String.GetHashCode из mscorwks.

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

Кто-нибудь может портировать эту функцию на безопасную реализацию??

4 ответа

Решение

Хеш-коды не предназначены для повторения на разных платформах или даже для нескольких запусков одной и той же программы в одной и той же системе. Вы идете не в ту сторону. Если вы не измените курс, ваш путь будет трудным, и однажды он может закончиться слезами.

Какую реальную проблему вы хотите решить? Можно ли написать свою собственную хеш-функцию, либо как метод расширения, либо как GetHashCode реализация класса-обертки и использовать его вместо этого?

Во-первых, Джон прав; это дурацкое поручение. Внутренние отладочные сборки фреймворка, который мы используем, чтобы "съесть свою собачью еду", ежедневно меняют алгоритм хэширования, чтобы не допустить людей к созданию систем - даже систем тестирования - которые полагаются на ненадежные детали реализации, которые задокументированы как подлежащие изменению в любое время.

Вместо того, чтобы закреплять эмуляцию системы, которая задокументирована как непригодная для эмуляции, я рекомендую сделать шаг назад и спросить себя, почему вы пытаетесь сделать что-то столь опасное. Это действительно требование?

Во-вторых, Stackru - это сайт технических вопросов и ответов, а не сайт "делай мою работу для меня бесплатно". Если вы одержимы этой опасной вещью и вам нужен кто-то, кто может переписать небезопасный код в эквивалентный безопасный код, тогда я рекомендую вам нанять того, кто может сделать это для вас.

Хотя все предупреждения, приведенные здесь, действительны, они не отвечают на вопрос. У меня была ситуация, в которой GetHashCode(), к сожалению, уже использовался в качестве постоянного значения в рабочей среде, и у меня не было выбора, кроме как повторно реализовать его, используя стандартный 32-разрядный алгоритм x86 (little-endian).NET 2.0. Я перекодировал без небезопасных, как показано ниже, и это, кажется, работает. Надеюсь, это кому-нибудь поможет.

// 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 = (int)value[i + 1];
        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);
}

Следующее точно воспроизводит значение по умолчанию String хэш-коды на .NET 4.7 (и, возможно, раньше). Это хеш-код, заданный:

  • По умолчанию на String пример: "abc".GetHashCode()
  • StringComparer.Ordinal.GetHashCode("abc")
  • Различный String методы, которые принимают StringComparison.Ordinal перечисление.
  • System.Globalization.CompareInfo.GetStringComparer(CompareOptions.Ordinal)

При тестировании сборок выпусков с полной JIT-оптимизацией эти версии скромно опережают встроенный код.NET, а также прошли тщательное модульное тестирование на точную эквивалентность с .NET поведение. Обратите внимание, что существуют отдельные версии для x86 и x64. Ваша программа, как правило, должна включать в себя оба; ниже соответствующих списков кодов находится система вызова, которая выбирает соответствующую версию во время выполнения.

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);
}
Другие вопросы по тегам