32-битная хеш-функция для объектов C#

Я хочу переопределить метод GetHashCode() объекта во всех моих классах. Этот метод возвращает Int32. Все известные мне криптографические хеш-функции возвращают значения, которые не помещаются в 32-битное целое число. Я хочу максимально избежать столкновений. Должен ли я обрезать безопасный хэш, такой как SHA-что угодно, или использовать 32-битный хэш? Если вы используете 32-битный хэш, какой 32-битный хэш лучше всего использовать?


person Charles Perniciaro III    schedule 18.04.2013    source источник
comment
Почему вы хотите переопределить GetHashCode? Если у вас нет действительно веских причин для этого, и вы понимаете все последствия, не делайте этого.   -  person Daniel Mann    schedule 19.04.2013
comment
Хеширование для обеспечения безопасности и хеширование для быстрой проверки личности — это две совершенно разные проблемы. Для идентификационного хэша вам все равно, легко ли получить новые экземпляры, которые производят тот же самый хэш; все, что вам нужно, это то, чтобы он был достаточно хорошо распределен по типичным образцам, которые вы используете, и чтобы его можно было быстро вычислить.   -  person Dan Bryant    schedule 19.04.2013
comment
Вам не нужен криптографический хеш, вам нужен быстрый, хорошо распределенный хэш. Криптографические хэши часто разрабатываются как можно более медленными и не всегда могут быть хорошо распределены. Как говорит Дэниел, не делайте этого, если вы полностью не понимаете, что делаете.   -  person TheEvilPenguin    schedule 19.04.2013
comment
blogs.msdn.com/b/securitytools/archive/2009/08/27/   -  person Robert Harvey    schedule 19.04.2013
comment
Вы, вероятно, захотите создать другой метод, не для переопределения GetHashCode, так как вы собираетесь использовать его для совершенно другой цели, чем для GetHashCode.   -  person Andrew Savinykh    schedule 19.04.2013
comment
Я переопределяю Equals(Object obj) во всех своих классах и хочу переопределить GetHashCode(), потому что он выдает предупреждение для всех из них. Если вы переопределяете Equals(), компилятор хочет, чтобы вы также переопределяли GetHashCode(), и я хотел бы сделать это правильно.   -  person Charles Perniciaro III    schedule 19.04.2013
comment
Прочтите и поймите мою статью на эту тему, прежде чем писать какой-либо код: blogs.msdn.com/b/ericlippert/archive/2011/02/28/   -  person Eric Lippert    schedule 19.04.2013
comment
@CharlesPerniciaroIII Обычно это достигается с помощью некоторой операции над значениями из GetHashCode() для членов класса. Можете ли вы опубликовать код с членами класса и их типами?   -  person TheEvilPenguin    schedule 19.04.2013
comment
График ожидаемого количества коллизий 32-битных хэшей, если хэш случайным образом распределяется по 32-битному пространству, см. на странице blogs.msdn.com/b/ericlippert/archive/2010/03/22/   -  person Eric Lippert    schedule 19.04.2013
comment
Спасибо за ваши ответы.   -  person Charles Perniciaro III    schedule 19.04.2013


Ответы (4)


Немного информации для всех. GetHashCode() на разных платформах .NET отличается. Например: «Hello».GetHashCode() в .NET 2.0 и «Hello».GetHashCode() в .NET 4.0 дают разные результаты. Следовательно, почему вы не можете сериализовать HashTables или Dictionary из коробки с помощью .NET.

Реализация собственных хеш-алгоритмов обеспечивает согласованность на разных платформах. Просто чтобы вы знали, вы не хотите идти меньше, чем Int32. Мой совет - придерживаться Int64 (длинный). Таким образом, у вас будет меньше коллизий, что и является целью хеширования :) Это библиотека, которую я написал много лет назад. У каждого алгоритма Hash есть свои плюсы и минусы (скорость против наименьшего количества столкновений). Эта конкретная версия использует строки в качестве входных данных, но вы можете изменить их по своему усмотрению:

static public class StringHash
    {
        //---------------------------------------------------------------------
        static public Int64 RSHash(String str)
        {
            const Int32 b = 378551;
            Int32 a = 63689;
            Int64 hash = 0;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = hash * a + str[i];
                a = a * b;
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 JSHash(String str)
        {
            Int64 hash = 1315423911;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash ^= ((hash << 5) + str[i] + (hash >> 2));
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 ELFHash(String str)
        {
            Int64 hash = 0;
            Int64 x = 0;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = (hash << 4) + str[i];

                if ((x = hash & 0xF0000000L) != 0)
                {
                    hash ^= (x >> 24);
                }
                hash &= ~x;
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 BKDRHash(String str)
        {
            const Int64 seed = 131; // 31 131 1313 13131 131313 etc..
            Int64 hash = 0;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = (hash * seed) + str[i];
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 SDBMHash(String str)
        {
            Int64 hash = 0;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = str[i] + (hash << 6) + (hash << 16) - hash;
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 DJBHash(String str)
        {
            Int64 hash = 5381;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = ((hash << 5) + hash) + str[i];
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 DEKHash(String str)
        {
            Int64 hash = str.Length;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = ((hash << 5) ^ (hash >> 27)) ^ str[i];
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 BPHash(String str)
        {
            Int64 hash = 0;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash = hash << 7 ^ str[i];
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 FNVHash(String str)
        {
            Int64 fnv_prime = 0x811C9DC5;
            Int64 hash = 0;

            for (Int32 i = 0; i < str.Length; i++)
            {
                hash *= fnv_prime;
                hash ^= str[i];
            }

            return hash;
        }
        //---------------------------------------------------------------------
        static public Int64 APHash(String str)
        {
            Int64 hash = 0xAAAAAAAA;

            for (Int32 i = 0; i < str.Length; i++)
            {
                if ((i & 1) == 0)
                {
                    hash ^= ((hash << 7) ^ str[i] * (hash >> 3));
                }
                else
                {
                    hash ^= (~((hash << 11) + str[i] ^ (hash >> 5)));
                }
            }

            return hash;
        }
    }
person code5    schedule 19.04.2013

Эрик Липперт создал отличная запись в блоге о том, как правильно реализовать метод GetHashCode(). Вы должны помнить, что целью GetHashCode() является помещение объектов в хеш-таблицу. Использование его для этой цели означает, что вы, скорее всего, захотите перебрать его или отсортировать в будущем. Если для этого вы используете криптографические функции, ваша итерация или процедура сортировки будут выполняться очень медленно. Функции шифрования предназначены для защиты данных, а не для их уникальной идентификации. Прочитайте сообщение в блоге Эрика Липперта. Это поможет вам

person Icemanind    schedule 19.04.2013

Вы можете реализовать GetHashCode, усекая хэш SHA. Но вы, вероятно, не должны.

Назначение GetHashCode — разрешить вставку объектов в хеш-таблицы. Целью хеш-таблиц является оптимизация поиска: в среднем поиск ключа в хэш-таблице требует всего O(1) времени по сравнению с O(log n) для дерева или O(n) для несортированного списка.

Вы действительно хотите, чтобы ваш метод GetHashCode минимизировал коллизии, чтобы предотвратить вырождение поиска в хеш-таблице до времени O(n). Но вы также хотите, чтобы они были быстрыми, потому что весь смысл хэш-таблиц заключается в оптимизации. Если вычисление вашего хеш-кода занимает много времени, вы могли бы просто сохранить свои данные в файле List.

Криптографические хэши работают медленно. Обычно они разработаны таким образом, чтобы препятствовать атакам методом грубой силы. Это делает их непригодными для использования с GetHashCode.

Итак, как следует реализовать GetHashCode? Простой, часто используемый подход состоит в том, чтобы просто объединить XOR всех переменных-членов, которые используются в вашей функции Equals.

struct Complex
{
    double real;
    double imag;

    public override int GetHashCode()
    {
        return real.GetHashCode() ^ imag.GetHashCode();
    }

    // ...
}

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

class MyClass
{
    int[] data;

    public override int GetHashCode()
    {
        int result = 0;

        foreach (int n in data)
        {
            result = result * 41 + n;
        }

        return result;
    }

    // ...
}

Если ваш класс содержит большой объем данных для хеширования, вы можете сохранить хеш-код в переменной-члене и предварительно вычислить его во время построения, чтобы GetHashCode() мог просто использовать эту переменную.

person dan04    schedule 19.04.2013
comment
Проблема с XOR заключается в том, что если real и image одинаковы, в результате вы всегда получите 0. - person svick; 19.04.2013
comment
@svick Вау, мне это не пришло в голову, когда я просматривал реализации BCL GetHashCode(). new Point(x, x).GetHashCode() дает 0 для любого значения x - person TheEvilPenguin; 19.04.2013

Чем короче ширина хеш-значения, тем выше вероятность возникновения коллизий. Поскольку Int32 хранит максимум 4294967296 различных значений, вам необходимо подумать, будет ли оно содержать достаточно уникальное значение для ваших целей, что будет зависеть от того, предназначено ли это для проверки безопасности или проверки личности.

Мне интересно, почему вы хотите переопределить GetHashCode(), должно ли значение умещаться в 32 бита? Если да, то почему?

person m.edmondson    schedule 18.04.2013
comment
Я переопределяю Equals(Object obj) во всех своих классах и хочу переопределить GetHashCode(), потому что он выдает предупреждение для всех из них. Если вы переопределяете Equals(), компилятор хочет, чтобы вы также переопределяли GetHashCode(), и я хотел бы сделать это правильно. - person Charles Perniciaro III; 19.04.2013
comment
Почему вы переопределяете GetHashCode() и, что еще более интригующе, почему вы переопределяете Equals(). Я хотел бы знать вашу проблему, чтобы сформулировать лучшее решение. - person m.edmondson; 19.04.2013
comment
Я переопределяю Equals(), потому что хочу сравнивать свои объекты в соответствии со своими спецификациями, а не реализацией по умолчанию. - person Charles Perniciaro III; 19.04.2013
comment
Я не ответил на ваш другой вопрос: GetHashCode() возвращает 32-битное целое число, поэтому мне нужно вписать в него этот хеш, как бы он ни был реализован. Я не могу вернуть хэш SHA или что-то в этом роде, потому что это намного больше битов. - person Charles Perniciaro III; 19.04.2013
comment
«Это будет зависеть от того, предназначено ли это для безопасности или проверки личности». Вы не должны никогда использовать для этого GetHashCode(), независимо от того, достаточно ли вам 32 бит или нет. - person svick; 19.04.2013
comment
@CharlesPerniciaroIII: Вы правы, что только количество битов затрудняет использование крипто-хэша в качестве GetHashCode. Но в целом криптохэши и GetHashCode пытаются решить совершенно разные проблемы; характеристики хорошего криптохэша часто противоположны хорошему балансировщику хеш-таблиц. Балансировка хеш-таблицы и подписание документа не имеют почти ничего общего; то, что обе операции включают в себя вычисление хэша, вызывает сожаление. - person Eric Lippert; 19.04.2013