Хэш-функция, которая создает короткие хэши?

есть ли один способ шифрования, который может принимать строку любой длины и создавать хэш-символ sub-10? Я хочу создать достаточно уникальные идентификаторы, но на основе содержимого сообщения, а не случайным образом.

Я могу жить с ограничением сообщений целочисленными значениями, хотя, если строки произвольной длины невозможны. Однако в этом случае хэш не должен быть одинаковым для двух последовательных целых чисел.

8 ответов


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

например, в Python:

>>> import hashlib
>>> hash = hashlib.sha1("my message".encode("UTF-8")).hexdigest()
>>> hash
'104ab42f1193c336aa2cf08a2c946d5c6fd0fcdb'
>>> hash[:10]
'104ab42f11'

Если вам не нужен алгоритм, который силен против преднамеренной модификации, я нашел алгоритм под названием adler32 это дает довольно короткие (~8 символов) результаты. Выберите его из выпадающего списка здесь, чтобы попробовать его:

http://www.sha1-online.com/


вам нужно хэшировать содержимое, чтобы придумать дайджест. Есть много доступных хэшей, но 10-символы довольно малы для результирующего набора. Назад люди использовали CRC-32, который производит 33-битный хэш (в основном 4 символа плюс один бит). Существует также CRC-64, который производит 65-битный хэш. MD5, который производит 128-битный хэш (16 байт / символов), считается сломанным для криптографических целей, потому что можно найти два сообщения, которые имеют один и тот же хэш. Это должно пойти, не говоря, что каждый раз, когда вы создаете 16-байтовый дайджест из сообщения произвольной длины, вы получите дубликаты. Чем короче дайджест, тем больше риск столкновений.

однако ваша озабоченность тем, что хэш не будет похож на два последовательных сообщения (будь то целые числа или нет), должна быть истинной со всеми хэшами. Даже одно битовое изменение в исходном сообщении должно привести к совершенно другому дайджесту.

Итак, используя что-то вроде CRC-64 (и base-64'ing результате) следует получить вас в районе, который вы ищете.


вы можете использовать существующий алгоритм хэша, который производит что-то короткое, например MD5 (128 бит) или SHA1 (160). Затем вы можете сократить это еще больше, XORing разделы дайджеста с другими разделами. Это увеличит вероятность столкновений, но не так плохо, как просто усечение дайджеста.

кроме того, можно добавить длину исходных данных как часть результата, чтобы сделать его более уникальным. Например, XORing первой половины дайджеста MD5 со второй половиной будет результат в 64 бит. Добавьте 32 бита для длины данных (или ниже, если вы знаете, что длина всегда будет вписываться в меньшее количество битов). Это приведет к 96-битному (12-байтовому) результату, который вы можете превратить в 24-символьную шестнадцатеричную строку. В качестве альтернативы вы можете использовать кодировку base 64, чтобы сделать ее еще короче.


просто суммируя ответ, который был мне полезен (отмечая комментарий @erasmospunk об использовании кодировки base-64). Моя цель состояла в том, чтобы иметь короткую строку, которая была в основном уникальный...

Я не эксперт, поэтому, пожалуйста, исправьте это, если у него есть какие-либо вопиющие ошибки (в Python снова, как принятый ответ):

import base64
import hashlib
import uuid

unique_id = uuid.uuid4()
# unique_id = UUID('8da617a7-0bd6-4cce-ae49-5d31f2a5a35f')

hash = hashlib.sha1(str(unique_id).encode("UTF-8"))
# hash.hexdigest() = '882efb0f24a03938e5898aa6b69df2038a2c3f0e'

result = base64.b64encode(hash.digest())
# result = b'iC77DySgOTjliYqmtp3yA4osPw4='

на result здесь используется больше, чем просто шестнадцатеричные символы (что вы получите, если вы использовали hash.hexdigest()), поэтому вероятность столкновения меньше (то есть, должно быть безопаснее усечь, чем шестнадцатеричный дайджест).

Примечание: использование UUID4 (random). См.http://en.wikipedia.org/wiki/Universally_unique_identifier для других типов.


вы можете использовать библиотеку hashids, которая имеет реализации для PHP, Javascript, Python и т. д. Для получения более подробной информации проверьте этой ссылке


Если вам нужно "sub-10-character hash" вы могли бы использовать Флетчер-32 алгоритм, который производит 8 символов хэша (32 бита),CRC-32 или Адлер-32.

CRC-32 медленнее, чем Adler32 в 20% - 100%.

Флетчер-32 несколько надежнее, чем Адлер-32. Он имеет более низкую вычислительную стоимость, чем контрольная сумма Adler:сравнение Флетчер и Адлер.

пример программы с несколькими Fletcher реализации приведены ниже:

    #include <stdio.h>
    #include <string.h>
    #include <stdint.h> // for uint32_t

    uint32_t fletcher32_1(const uint16_t *data, size_t len)
    {
            uint32_t c0, c1;
            unsigned int i;

            for (c0 = c1 = 0; len >= 360; len -= 360) {
                    for (i = 0; i < 360; ++i) {
                            c0 = c0 + *data++;
                            c1 = c1 + c0;
                    }
                    c0 = c0 % 65535;
                    c1 = c1 % 65535;
            }
            for (i = 0; i < len; ++i) {
                    c0 = c0 + *data++;
                    c1 = c1 + c0;
            }
            c0 = c0 % 65535;
            c1 = c1 % 65535;
            return (c1 << 16 | c0);
    }

    uint32_t fletcher32_2(const uint16_t *data, size_t l)
    {
        uint32_t sum1 = 0xffff, sum2 = 0xffff;

        while (l) {
            unsigned tlen = l > 359 ? 359 : l;
            l -= tlen;
            do {
                sum2 += sum1 += *data++;
            } while (--tlen);
            sum1 = (sum1 & 0xffff) + (sum1 >> 16);
            sum2 = (sum2 & 0xffff) + (sum2 >> 16);
        }
        /* Second reduction step to reduce sums to 16 bits */
        sum1 = (sum1 & 0xffff) + (sum1 >> 16);
        sum2 = (sum2 & 0xffff) + (sum2 >> 16);
        return (sum2 << 16) | sum1;
    }

    int main()
    {
        char *str1 = "abcde";  
        char *str2 = "abcdef";

        size_t len1 = (strlen(str1)+1) / 2; //  '' will be used for padding 
        size_t len2 = (strlen(str2)+1) / 2; // 

        uint32_t f1 = fletcher32_1(str1,  len1);
        uint32_t f2 = fletcher32_2(str1,  len1);

        printf("%u %X \n",    f1,f1);
        printf("%u %X \n\n",  f2,f2);

        f1 = fletcher32_1(str2,  len2);
        f2 = fletcher32_2(str2,  len2);

        printf("%u %X \n",f1,f1);
        printf("%u %X \n",f2,f2);

        return 0;
    }

выход:

4031760169 F04FC729                                                                                                                                                                                                                              
4031760169 F04FC729                                                                                                                                                                                                                              

1448095018 56502D2A                                                                                                                                                                                                                              
1448095018 56502D2A                                                                                                                                                                                                                              

договаривается с тест векторы:

"abcde"  -> 4031760169 (0xF04FC729)
"abcdef" -> 1448095018 (0x56502D2A)

Adler-32 имеет слабость для коротких сообщений с несколькими сотнями байтов, потому что контрольные суммы для этих сообщений имеют плохое покрытие 32 доступных битов. Проверьте это:

алгоритм Adler32 недостаточно сложен, чтобы конкурировать с сопоставимыми контрольными суммами.


недавно мне нужно было что-то вроде простой функции сокращения строки. В принципе, код выглядел примерно так (код C / C++ впереди):

size_t ReduceString(char *Dest, size_t DestSize, const char *Src, size_t SrcSize, bool Normalize)
{
    size_t x, x2 = 0, z = 0;

    memset(Dest, 0, DestSize);

    for (x = 0; x < SrcSize; x++)
    {
        Dest[x2] = (char)(((unsigned int)(unsigned char)Dest[x2]) * 37 + ((unsigned int)(unsigned char)Src[x]));
        x2++;

        if (x2 == DestSize - 1)
        {
            x2 = 0;
            z++;
        }
    }

    // Normalize the alphabet if it looped.
    if (z && Normalize)
    {
        unsigned char TempChr;
        y = (z > 1 ? DestSize - 1 : x2);
        for (x = 1; x < y; x++)
        {
            TempChr = ((unsigned char)Dest[x]) & 0x3F;

            if (TempChr < 10)  TempChr += '0';
            else if (TempChr < 36)  TempChr = TempChr - 10 + 'A';
            else if (TempChr < 62)  TempChr = TempChr - 36 + 'a';
            else if (TempChr == 62)  TempChr = '_';
            else  TempChr = '-';

            Dest[x] = (char)TempChr;
        }
    }

    return (SrcSize < DestSize ? SrcSize : DestSize);
}

он, вероятно, имеет больше коллизий, чем можно было бы пожелать, но он не предназначен для использования в качестве криптографической хэш-функции. Вы можете попробовать различные множители (т. е. изменить 37 на другое простое число), если у вас слишком много столкновений. Одна из интересных особенностей этого фрагмента заключается в том, что когда Src короче чем Dest, Dest заканчивается входной строкой as-is (0 * 37 + value = value). Если вы хотите что-то" читаемое " в конце процесса, Normalize отрегулирует преобразованные байты за счет увеличения коллизий.

источник:

https://github.com/cubiclesoft/cross-platform-cpp/blob/master/sync/sync_util.cpp