ฟังก์ชันแฮชที่สร้างแฮชสั้น ๆ ?


102

มีวิธีการเข้ารหัสที่สามารถใช้สตริงที่มีความยาวเท่าใดก็ได้และสร้างแฮชย่อย 10 อักขระหรือไม่? ฉันต้องการสร้าง ID ที่ไม่ซ้ำกันอย่างสมเหตุสมผล แต่ขึ้นอยู่กับเนื้อหาของข้อความแทนที่จะเป็นแบบสุ่ม

ฉันสามารถมีชีวิตอยู่ได้ด้วยการ จำกัด ข้อความให้เป็นค่าจำนวนเต็มแม้ว่าสตริงที่มีความยาวตามอำเภอใจเป็นไปไม่ได้ อย่างไรก็ตามแฮชต้องไม่เหมือนกันสำหรับจำนวนเต็มสองจำนวนติดต่อกันในกรณีนั้น


ที่เรียกว่าแฮช มันจะไม่ซ้ำกัน
SLaks

1
นี่เป็นปัญหาการตัดแฮชเช่นกันดังนั้นโปรดดูที่stackoverflow.com/q/4784335
Peter Krauss

2
FYI ดูรายการฟังก์ชันแฮชในวิกิพีเดีย
Basil Bourque

คำตอบ:


80

คุณสามารถใช้อัลกอริทึมแฮชที่มีอยู่ทั่วไป (เช่น SHA-1) ซึ่งจะให้ผลลัพธ์ที่ยาวนานกว่าที่คุณต้องการเล็กน้อย เพียงแค่ตัดผลลัพธ์ให้มีความยาวตามที่ต้องการซึ่งอาจจะดีพอ

ตัวอย่างเช่นใน Python:

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

3
ฟังก์ชันแฮชที่เหมาะสมใด ๆ สามารถตัดทอนได้
ประธาน James K. Polk

92
สิ่งนี้จะไม่เพิ่มความเสี่ยงของการปะทะกันในระดับที่สูงขึ้นหรือ?
Gabriel Sanmartin

144
@erasmospunk: การเข้ารหัสด้วย base64 ไม่ได้ทำอะไรเลยสำหรับความต้านทานการชนกันเนื่องจากถ้าhash(a)ชนด้วยhash(b)แล้วจะbase64(hash(a))ชนกับbase64(hash(b)).
Greg Hewgill

57
@GregHewgill คุณพูดถูก แต่เราไม่ได้พูดถึงอัลกอริทึมแฮชดั้งเดิมที่ชนกัน (ใช่sha1ชนกัน แต่นี่เป็นอีกเรื่องหนึ่ง) หากคุณมีแฮช 10 อักขระคุณจะได้เอนโทรปีสูงขึ้นหากเข้ารหัสด้วยbase64vs base16(หรือฐานสิบหก) สูงขึ้นแค่ไหน? เมื่อbase16คุณได้รับข้อมูล 4 บิตต่ออักขระโดยbase64ตัวเลขนี้คือ6 บิต / ตัวอักษร แฮช "hex" จำนวน 10 ตัวจะมีเอนโทรปี 40 บิตในขณะที่ฐาน 64 60 บิต เลยทนกว่าเล็กน้อยขออภัยถ้าถ่ายไม่ชัด
John L. Jegutanis

21
@erasmospunk: โอ้ฉันเข้าใจว่าคุณหมายถึงอะไรใช่ถ้าคุณมีขนาดคงที่ จำกัด สำหรับผลลัพธ์ของคุณคุณสามารถแพ็คบิตที่สำคัญมากขึ้นด้วยการเข้ารหัส base64 เทียบกับการเข้ารหัสฐานสิบหก
Greg Hewgill

46

หากคุณไม่ต้องการอัลกอริทึมที่แข็งแกร่งต่อการแก้ไขโดยเจตนาฉันพบอัลกอริทึมที่เรียกว่าadler32ซึ่งให้ผลลัพธ์สั้น ๆ (~ 8 อักขระ) เลือกจากเมนูแบบเลื่อนลงที่นี่เพื่อทดลองใช้:

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


2
มันเก่ามากไม่น่าเชื่อถือ
Mascarpone

1
@Mascarpone "ไม่ค่อยน่าเชื่อถือ" - แหล่งที่มา? มันมีข้อ จำกัด ถ้าคุณรู้จักพวกเขาก็ไม่สำคัญว่าอายุเท่าไหร่
BT

8
@Mascarpone "จุดอ่อนน้อย" - อีกครั้งสิ่งที่จุดอ่อน? ทำไมคุณถึงคิดว่าอัลกอริทึมนี้ไม่สมบูรณ์แบบ 100% สำหรับการใช้งานของ OP
BT

3
@Mascarpone OP ไม่ได้บอกว่าพวกเขาต้องการแฮชระดับ crypto OTOH, Adler32 คือการตรวจสอบไม่ใช่แฮชดังนั้นจึงอาจไม่เหมาะสมขึ้นอยู่กับว่า OP กำลังทำอะไรอยู่
PM 2Ring

2
มีข้อแม้ประการหนึ่งสำหรับ Adler32 คือการอ้างถึง Wikipedia : Adler-32 มีจุดอ่อนสำหรับข้อความสั้นที่มีไม่กี่ร้อยไบต์เนื่องจากการตรวจสอบข้อความเหล่านี้มีการครอบคลุมที่ไม่ดีของ 32 บิตที่มีอยู่
Basil Bourque

13

คุณต้องแฮชเนื้อหาเพื่อให้ได้ข้อมูลสรุป มีแฮชมากมาย แต่ 10 ตัวอักษรค่อนข้างเล็กสำหรับชุดผลลัพธ์ ในทางกลับกันผู้คนใช้ CRC-32 ซึ่งสร้างแฮช 33 บิต (โดยทั่วไป 4 ตัวอักษรบวกหนึ่งบิต) นอกจากนี้ยังมี CRC-64 ซึ่งสร้างแฮช 65 บิต MD5 ซึ่งสร้างแฮช 128 บิต (16 ไบต์ / อักขระ) ถือว่าใช้งานไม่ได้เนื่องจากวัตถุประสงค์ในการเข้ารหัสเนื่องจากสามารถพบข้อความสองข้อความที่มีแฮชเหมือนกัน ควรดำเนินการโดยไม่ต้องบอกว่าเมื่อใดก็ตามที่คุณสร้างข้อความย่อยขนาด 16 ไบต์จากข้อความที่มีความยาวตามอำเภอใจคุณจะพบกับรายการที่ซ้ำกัน ยิ่งย่อยสั้นเท่าไหร่ก็ยิ่งเสี่ยงต่อการชนกันมากขึ้นเท่านั้น

อย่างไรก็ตามข้อกังวลของคุณที่ว่าแฮชไม่เหมือนกันสำหรับสองข้อความติดต่อกัน (ไม่ว่าจะเป็นจำนวนเต็มหรือไม่ก็ตาม) ควรเป็นจริงกับแฮชทั้งหมด แม้แต่การเปลี่ยนแปลงเพียงเล็กน้อยในข้อความต้นฉบับก็ควรให้ผลลัพธ์ที่แตกต่างกันอย่างมาก

ดังนั้นการใช้บางอย่างเช่น CRC-64 (และผลลัพธ์ที่เป็นฐาน 64) จะทำให้คุณอยู่ในละแวกใกล้เคียงที่คุณกำลังมองหา


1
CRC 'การแฮช SHA-1 และฐาน -64' ผลลัพธ์ทำให้ ID ผลลัพธ์มีความทนทานต่อการชนกันมากขึ้นหรือไม่?

5
"อย่างไรก็ตามความกังวลของคุณที่ว่าแฮชไม่เหมือนกันสำหรับสองข้อความติดต่อกัน [... ] ควรเป็นจริงกับแฮชทั้งหมด" - นั่นไม่จำเป็นต้องเป็นความจริง ตัวอย่างเช่นสำหรับฟังก์ชันแฮชที่ใช้สำหรับการทำคลัสเตอร์หรือการตรวจจับการโคลนสิ่งที่ตรงกันข้ามคือความจริง: คุณต้องการให้เอกสารที่คล้ายกันให้ค่าแฮชที่ใกล้เคียงกัน (หรือเหมือนกัน) ตัวอย่างที่รู้จักกันดีของอัลกอริทึมแฮชที่ออกแบบมาโดยเฉพาะเพื่อให้ได้ค่าที่เหมือนกันสำหรับอินพุตที่คล้ายกันคือ Soundex
Jörg W Mittag

ฉันใช้แฮชเพื่อตรวจสอบลายเซ็นของข้อความ โดยพื้นฐานแล้วสำหรับข้อความที่ทราบและลายเซ็นที่ระบุแฮชจะต้องถูกต้อง ฉันไม่สนใจหรอกว่ามันจะมีผลบวกเท็จเล็กน้อยหรือไม่ เป็นที่ยอมรับโดยสิ้นเชิง ขณะนี้ฉันใช้แฮช SHA-512 ที่ถูกตัดทอนที่บีบอัดด้วย base62 (สิ่งที่ฉันทำอย่างรวดเร็ว) เพื่อความสะดวก

@ JörgWMittagจุดที่ยอดเยี่ยมใน SoundEx ฉันยืนแก้ไข แฮชทั้งหมดไม่ได้มีลักษณะเหมือนกัน
John

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สำหรับประเภทอื่น ๆ


8

หากคุณต้องการ"sub-10-character hash" คุณสามารถใช้อัลกอริทึมFletcher-32ซึ่งสร้างแฮชอักขระ 8 ตัว (32 บิต), CRC-32หรือAdler-32 Adler-32

CRC-32 ช้ากว่า Adler32 โดย 20% - 100%

Fletcher-32 มีความน่าเชื่อถือมากกว่า Adler-32 เล็กน้อย มีต้นทุนการคำนวณที่ต่ำกว่าการเปรียบเทียบ Adler checksum: Fletcher vs 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; //  '\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 ไม่ซับซ้อนพอที่จะแข่งขันกับเช็คซัมที่เทียบเคียงกันได้


7

คุณสามารถใช้อัลกอริทึมแฮชที่มีอยู่ซึ่งสร้างสิ่งที่สั้นเช่น MD5 (128 บิต) หรือ SHA1 (160) จากนั้นคุณสามารถย่อให้สั้นลงได้อีกโดย XORing ส่วนของไดเจสต์กับส่วนอื่น ๆ วิธีนี้จะเพิ่มโอกาสในการชนกัน แต่ไม่เลวร้ายเท่ากับการตัดทอนการสรุปย่อย

นอกจากนี้คุณสามารถใส่ความยาวของข้อมูลต้นฉบับเป็นส่วนหนึ่งของผลลัพธ์เพื่อให้มีลักษณะเฉพาะมากขึ้น ตัวอย่างเช่น XORing ครึ่งแรกของ MD5 Digest กับครึ่งหลังจะทำให้ได้ 64 บิต เพิ่ม 32 บิตสำหรับความยาวของข้อมูล (หรือต่ำกว่าถ้าคุณรู้ว่าความยาวนั้นจะพอดีกับบิตที่น้อยกว่าเสมอ) นั่นจะทำให้ได้ผลลัพธ์ 96 บิต (12 ไบต์) ที่คุณสามารถเปลี่ยนเป็นสตริงเลขฐานสิบหก 24 อักขระได้ คุณสามารถใช้การเข้ารหัส 64 ฐานเพื่อทำให้สั้นลงได้


2
FWIW เรียกว่า XOR-folding
PM 2Ring

6

เพียงเรียกใช้สิ่งนี้ในเทอร์มินัล (บน MacOS หรือ Linux):

crc32 <(echo "some string")

ยาว 8 อักขระ


6

คุณสามารถใช้ไลบรารีhashlibสำหรับ Python shake_128และshake_256ขั้นตอนวิธีการให้ hashes ยาวตัวแปร นี่คือรหัสการทำงานบางส่วน (Python3):

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

ขอให้สังเกตว่ามีพารามิเตอร์ยาวx (5 ในตัวอย่าง) ฟังก์ชั่นส่งกลับค่าแฮชของความยาว2x


2

ตอนนี้เป็นปี 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 :: bloaty เมื่อโค้ดพิเศษไม่กี่บรรทัดก็เพียงพอแล้ว) ไม่มีอะไรโง่ที่นี่ มีการคิดอย่างรอบคอบเพื่อจัดการกับข้อ จำกัด ที่สำคัญใน Mac OSX ฉันไม่ต้องการจำนวนเต็ม สำหรับสิ่งนั้นฉันสามารถใช้ djb2 และยังคงหลีกเลี่ยงการใช้ std :: template
CubicleSoft

นี่ยังฟังดูงี่เง่า ทำไมคุณเคยใช้DestSizeมากกว่า 4 (32 บิต) เมื่อกัญชาตัวเองเป็นเส็งเคร็งเพื่อ? หากคุณต้องการความต้านทานการชนที่ให้โดยเอาต์พุตที่มีขนาดใหญ่กว่า int คุณจะต้องใช้ SHA
นาวิน

ดูจะไม่ใช่แฮชดั้งเดิมจริงๆ มีคุณสมบัติที่เป็นประโยชน์ซึ่งผู้ใช้สามารถประกาศขนาดสตริงในสถานที่ที่มีพื้นที่บัฟเฟอร์ จำกัด อย่างมากในบางระบบปฏิบัติการ (เช่น Mac OSX) และผลลัพธ์จะต้องพอดีกับโดเมนที่ จำกัด ของชื่อไฟล์จริงและพวกเขาไม่ต้องการเพียงแค่ตัดทอน ชื่อเนื่องจากว่าอาจทำให้เกิดการชนกัน (แต่สตริงที่สั้นกว่าจะถูกปล่อยให้อยู่คนเดียว) แฮชการเข้ารหัสไม่ใช่คำตอบที่ถูกต้องเสมอไปและ std :: hash ก็ไม่ใช่คำตอบที่ถูกต้องเสมอไป
CubicleSoft
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.