Хеш-функція, яка виробляє короткі хеші?


97

Чи існує спосіб шифрування, який може зайняти рядок будь-якої довжини і створити хеш, менший за 10 символів? Я хочу створити досить унікальні ідентифікатори, але на основі вмісту повідомлення, а не випадково.

Однак я можу жити, обмежуючи повідомлення цілими значеннями, якщо рядки довільної довжини неможливі. Однак хеш у такому випадку не повинен бути подібним для двох послідовних цілих чисел.


Це називається хеш. Це не буде унікальним.
Слакс

1
Це також проблема з усіченням хешу , тому див. Також stackoverflow.com/q/4784335
Пітер Краус,

2
FYI, див. Перелік хеш-функцій у Вікіпедії.
Василь Бурк,

Відповіді:


77

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

Наприклад, у Python:

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

2
Будь-яка розумна хеш-функція може бути усічена.
Президент Джеймс К. Полк

88
чи не підвищить це ризик зіткнення набагато вищий ступінь?
Габріель Санмартін

143
@erasmospunk: кодування з base64 нічого не робить для стійкості до зіткнень, оскільки якщо hash(a)зіткнеться з, hash(b)то base64(hash(a))також зіткнеться з base64(hash(b)).
Greg Hewgill

56
@GregHewgill ви маєте рацію, але ми не говоримо про вихідний хеш-алгоритм, що стикається (так, sha1стикається, але це вже інша історія). Якщо у вас хеш із 10 символів, ви отримуєте вищу ентропію, якщо він закодований символом base64vs base16(або шістнадцятковою). Як вище? Отримуючи base164 біти інформації на символ, base64цей показник дорівнює 6 біт / знак. Разом 10-символьний "шістнадцятковий" хеш матиме 40 біт ентропії, тоді як base64 60 бітів. Тож він трохи стійкіший, вибачте, якщо я не був надто зрозумілим.
Джон Л. Єгутаніс

20
@erasmospunk: О, я розумію, що ви маєте на увазі, так, якщо у вас обмежений фіксований розмір для вашого результату, ви можете упакувати більш значущі біти з кодуванням base64 проти шістнадцяткового кодування.
Greg Hewgill

46

Якщо вам не потрібен алгоритм, сильний проти навмисних модифікацій, я знайшов алгоритм під назвою adler32, який дає досить короткі (~ 8 символів) результати. Виберіть його зі спадного меню тут, щоб спробувати:

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


2
це дуже старий, не дуже надійний.
Маскарпоне

1
@Mascarpone "не дуже надійний" - джерело? Це має обмеження, якщо ви їх знаєте, не має значення, скільки йому років.
BT

8
@Mascarpone "менше слабких сторін" - знову ж таки, які слабкі сторони? Чому, на вашу думку, цей алгоритм не на 100% ідеальний для використання ОП?
BT

3
@Mascarpone В ОП не сказано, що вони хочуть криптографічний хеш. OTOH, Adler32 є контрольною сумою, а не хешем, тому він може бути непридатним, залежно від того, що OP фактично робить з ним.
PM 2Кольцо

2
Є одне застереження щодо Adler32, цитуючи Вікіпедію : Adler-32 має слабкі сторони щодо коротких повідомлень з кількома сотнями байтів, оскільки контрольна сума для цих повідомлень погано охоплює 32 доступних біта.
Василь Бурк,

13

Вам потрібно хешувати вміст, щоб скласти дайджест. Доступно багато хешів, але 10 символів досить мало для результату. Повертаючись назад, люди використовували CRC-32, який створює 33-бітний хеш (в основному 4 символи плюс один біт). Існує також CRC-64, який виробляє 65-розрядний хеш. MD5, який створює 128-бітний хеш (16 байт / символів), вважається непрацездатним для криптографічних цілей, оскільки можна знайти два повідомлення, що мають однаковий хеш. Само собою зрозуміло, що кожного разу, коли ви створюєте 16-байтовий дайджест із довільної довжини повідомлення, ви отримаєте дублікати. Чим коротший дайджест, тим більший ризик зіткнень.

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

Отже, використання чогось на кшталт CRC-64 (і base-64 'результат) повинно привести вас у сусідній район, який ви шукаєте.


1
Чи робить CRC хеш SHA-1, а потім базовий 64 'результат, робить отриманий ID більш стійким до зіткнення?

5
"Однак ваша стурбованість тим, що хеш не буде подібним протягом двох послідовних повідомлень [...], має бути справедливою з усіма хешами." - Це не обов’язково правда. Наприклад, для хеш-функцій, які використовуються для кластеризації або виявлення клонів, насправді вірно все навпаки: ви хочете, щоб подібні документи давали схожі (або навіть однакові) хеш-значення. Відомим прикладом хеш-алгоритму, який спеціально розроблений для отримання однакових значень для подібних входів, є Soundex.
Jörg W Mittag

Я використовую хеші для автентифікації підпису повідомлення. Отже, в основному для відомого повідомлення та вказаного підпису хеш повинен бути правильним. Мені байдуже, чи буде невеликий відсоток помилкових спрацьовувань. Це цілком прийнятно. В даний час для зручності використовую усічений хеш SHA-512, стислий з base62 (те, що я швидко збив).

@ JörgWMittag Відмінна оцінка на SoundEx. Я стою виправлений. Не всі хеші мають однакові характеристики.
Джон

12

Просто підсумовуючи корисну для мене відповідь (зазначивши коментар @ 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 (випадковий). Інші типи див. На веб-сайті http://en.wikipedia.org/wiki/Universally_unique_identifier .


7

Ви можете використовувати існуючий хеш-алгоритм, який створює щось коротке, наприклад MD5 (128 біт) або SHA1 (160). Тоді ви можете скоротити це додатково шляхом XORing розділів дайджесту з іншими розділами. Це збільшить ймовірність зіткнень, але не настільки поганим, як просто скорочення дайджесту.

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


2
FWIW, це відомо як XOR-складання.
PM 2Кольцо

7

Якщо вам потрібно, "sub-10-character hash" ви можете скористатися алгоритмом Fletcher-32 , який створює хеш із 8 символів (32 біти), CRC-32 або Adler-32 .

CRC-32 повільніший за Adler32 у 20 - 100%.

Флетчер-32 трохи надійніший за Адлер-32. Він має нижчі обчислювальні витрати, ніж контрольна сума Адлера: порівняння Флетчера і Адлера .

Зразок програми з кількома реалізаціями 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; //  '\0' 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 недостатньо складний, щоб конкурувати з порівнянними контрольними сумами .


6

Просто запустіть це в терміналі (на MacOS або Linux):

crc32 <(echo "some string")

Довжиною 8 символів.


4

Ви можете використовувати бібліотеку hashlib для Python. Алгоритми shake_128 та shake_256 забезпечують хеші змінної довжини. Ось деякий робочий код (Python3):

import hashlib
>>> my_string = 'hello shake'
>>> hashlib.shake_256(my_string.encode()).hexdigest(5)
'34177f6a0a'

Зауважте, що за допомогою параметра довжини x (наприклад 5) функція повертає хеш-значення довжини 2x .


1

Зараз 2019 рік, і є кращі варіанти. А саме, xxhash .

~ echo test | xxhsum                                                           
2d7f1808da1fa63c  stdin

Це посилання порушено. краще надати більш повну відповідь.
eri0o

0

Нещодавно мені знадобилося щось на зразок простої функції зменшення рядків. В основному, код виглядав приблизно так (код 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 закінчується вхідним рядком як є (0 * 37 + value = value). Якщо ви хочете щось «читабельне» в кінці процесу, Normalize відкоригує перетворені байти ціною збільшення зіткнень.

Джерело:

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


std :: hash не вирішує певних випадків використання (наприклад, уникає перетягування нездужливих шаблонів std ::, коли буде достатньо лише декількох додаткових рядків коду). Тут нічого дурного немає. Це було ретельно продумано, щоб мати справу з основними обмеженнями в Mac OSX. Я не хотів цілого числа. Для цього я міг використовувати djb2 і все одно уникав використання std :: templates.
CubicleSoft

Це все ще звучить безглуздо. Чому б ви коли-небудь використовували DestSizeбільше 4 (32 біт), коли сам хеш такий дерьмовий? Якщо ви хочете, щоб опір зіткненню забезпечував вихід, більший за int, ви б використовували SHA.
Navin

Подивіться, це насправді не традиційний хеш. Він має корисні властивості, де користувач може оголосити розмір рядка в місцях, де в певних ОС (наприклад, Mac OSX) надзвичайно обмежений буферний простір. І результат повинен входити до обмеженого домену реальних імен файлів, і вони не хочуть просто усікати назва, тому що ЦЕ МОЖЕ спричинити зіткнення (але коротші рядки залишаються самі). Криптографічний хеш - це не завжди правильна відповідь, а std :: hash - не завжди правильна відповідь.
CubicleSoft
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.