Hash-Funktion, die kurze Hashes erzeugt?

96

Gibt es eine Verschlüsselungsmethode, die eine Zeichenfolge beliebiger Länge annehmen und einen Hash mit weniger als 10 Zeichen erzeugen kann? Ich möchte einigermaßen eindeutige IDs erstellen, die jedoch nicht zufällig, sondern auf Nachrichteninhalten basieren.

Ich kann damit leben, die Nachrichten auf ganzzahlige Werte zu beschränken, wenn Zeichenfolgen beliebiger Länge unmöglich sind. In diesem Fall darf der Hash jedoch nicht für zwei aufeinanderfolgende Ganzzahlen ähnlich sein.

rath3r
quelle
Das nennt man einen Hash. Es wird nicht einzigartig sein.
SLaks
1
Dies ist auch ein Problem beim Abschneiden von Hashs
Peter Krauss
2
Zu Ihrer Information, siehe eine Liste der Hash-Funktionen in Wikipedia.
Basil Bourque

Antworten:

75

Sie können jeden allgemein verfügbaren Hash-Algorithmus (z. B. SHA-1) verwenden, wodurch Sie ein etwas längeres Ergebnis erzielen als erforderlich. Schneiden Sie das Ergebnis einfach auf die gewünschte Länge ab, was gut genug sein kann.

Zum Beispiel in Python:

>>> import hashlib
>>> hash = hashlib.sha1("my message".encode("UTF-8")).hexdigest()
>>> hash
'104ab42f1193c336aa2cf08a2c946d5c6fd0fcdb'
>>> hash[:10]
'104ab42f11'
Greg Hewgill
quelle
2
Jede vernünftige Hash-Funktion kann abgeschnitten werden.
Präsident James K. Polk
87
Würde dies nicht das Kollisionsrisiko in viel höherem Maße erhöhen?
Gabriel Sanmartin
142
@erasmospunk: Codieren mit base64 tut nichts für Kollision Widerstand, denn wenn hash(a)kollidiert mit hash(b)dann base64(hash(a))auch kollidiert mit base64(hash(b)).
Greg Hewgill
56
@ GregHewgill Sie haben Recht, aber wir sprechen nicht über die Kollision des ursprünglichen Hash-Algorithmus (ja, sha1kollidiert, aber dies ist eine andere Geschichte). Wenn Sie einen Hash mit 10 Zeichen haben, erhalten Sie eine höhere Entropie, wenn er mit base64vs base16(oder hex) codiert ist . Wie höher? Mit base16erhalten Sie 4 Bits pro Zeichen, mit base64dieser Figur ist 6bits / char. Insgesamt hat ein 10-Zeichen- "Hex" -Hash 40 Bit Entropie, während ein base64 60 Bit hat. Es ist also etwas widerstandsfähiger, sorry wenn ich nicht super klar war.
John L. Jegutanis
19
@erasmospunk: Oh, ich verstehe, was Sie meinen. Ja, wenn Sie eine begrenzte feste Größe für Ihr Ergebnis haben, können Sie mit Base64-Codierung im Vergleich zur Hex-Codierung signifikantere Bits einpacken.
Greg Hewgill
46

Wenn Sie keinen Algorithmus benötigen, der stark gegen absichtliche Änderungen ist, habe ich einen Algorithmus namens adler32 gefunden , der ziemlich kurze Ergebnisse (~ 8 Zeichen) liefert. Wählen Sie es hier aus der Dropdown-Liste aus, um es auszuprobieren:

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

BT
quelle
2
es ist sehr alt, nicht sehr zuverlässig.
Mascarpone
1
@Mascarpone "nicht sehr zuverlässig" - Quelle? Es hat Einschränkungen, wenn Sie sie kennen, spielt es keine Rolle, wie alt es ist.
BT
8
@Mascarpone "weniger Schwächen" - wieder welche Schwächen? Warum ist dieser Algorithmus Ihrer Meinung nach nicht zu 100% perfekt für die Verwendung des OP?
BT
3
@Mascarpone Das OP sagt nicht, dass sie einen Hash in Krypto-Qualität wollen. OTOH, Adler32 ist eine Prüfsumme, kein Hash, daher ist es möglicherweise nicht geeignet, je nachdem, was das OP tatsächlich damit macht.
PM 2Ring
2
Es gibt eine Einschränkung bei Adler32, die Wikipedia zitiert : Adler-32 hat eine Schwäche für Kurznachrichten mit einigen hundert Bytes, da die Prüfsummen für diese Nachrichten eine schlechte Abdeckung der 32 verfügbaren Bits aufweisen.
Basil Bourque
13

Sie müssen den Inhalt hashen, um eine Übersicht zu erhalten. Es sind viele Hashes verfügbar, aber 10 Zeichen sind für die Ergebnismenge ziemlich klein. Vor langer Zeit verwendeten die Leute CRC-32, das einen 33-Bit-Hash erzeugt (im Grunde 4 Zeichen plus ein Bit). Es gibt auch CRC-64, der einen 65-Bit-Hash erzeugt. MD5, das einen 128-Bit-Hash (16 Bytes / Zeichen) erzeugt, wird für kryptografische Zwecke als fehlerhaft betrachtet, da zwei Nachrichten gefunden werden können, die denselben Hash haben. Es versteht sich von selbst, dass Sie jedes Mal, wenn Sie einen 16-Byte-Digest aus einer Nachricht beliebiger Länge erstellen, Duplikate erhalten. Je kürzer die Verdauung ist, desto größer ist das Kollisionsrisiko.

Ihre Sorge, dass der Hash für zwei aufeinanderfolgende Nachrichten (ob Ganzzahlen oder nicht) nicht ähnlich ist, sollte jedoch bei allen Hashes zutreffen. Selbst eine einzelne Bitänderung in der ursprünglichen Nachricht sollte zu einem völlig anderen resultierenden Digest führen.

Wenn Sie also etwas wie CRC-64 verwenden (und das Ergebnis mit Base-64 erstellen), sollten Sie in die Nachbarschaft gelangen, nach der Sie suchen.

John
quelle
1
Macht das CRC'ing eines SHA-1-Hash und das anschließende Base-64'ing des Ergebnisses die resultierende ID widerstandsfähiger gegen Kollisionen?
5
"Ihre Sorge, dass der Hash für zwei [...] aufeinanderfolgende Nachrichten nicht ähnlich ist, sollte jedoch bei allen Hashes zutreffen." - Das stimmt nicht unbedingt. Für Hash-Funktionen, die zum Clustering oder zur Klonerkennung verwendet werden, gilt beispielsweise genau das Gegenteil: Sie möchten, dass ähnliche Dokumente ähnliche (oder sogar dieselben) Hash-Werte liefern. Ein bekanntes Beispiel für einen Hash-Algorithmus, der speziell entwickelt wurde, um identische Werte für ähnliche Eingaben zu erhalten, ist Soundex.
Jörg W Mittag
Ich verwende die Hashes zur Authentifizierung der Signatur der Nachricht. Grundsätzlich muss für eine bekannte Nachricht und eine bestimmte Signatur der Hash korrekt sein. Es ist mir jedoch egal, ob es einen kleinen Prozentsatz von Fehlalarmen gibt. Es ist völlig akzeptabel. Ich verwende derzeit den abgeschnittenen SHA-512-Hash, der mit base62 (etwas, das ich schnell erstellt habe) komprimiert wurde.
@ JörgWMittag Hervorragender Punkt auf SoundEx. Ich stehe korrigiert. Nicht alle Hashes haben die gleichen Eigenschaften.
John
12

Ich fasse nur eine Antwort zusammen, die für mich hilfreich war (unter Hinweis auf @ erasmospunks Kommentar zur Verwendung der Base-64-Codierung). Mein Ziel war es, eine kurze Saite zu haben, die größtenteils einzigartig war ...

Ich bin kein Experte, bitte korrigieren Sie dies, wenn es irgendwelche offensichtlichen Fehler gibt (in Python wieder wie die akzeptierte Antwort):

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='

In resultdiesem Fall werden mehr als nur Hex-Zeichen verwendet (was Sie erhalten würden, wenn Sie es verwenden würden hash.hexdigest()), sodass es weniger wahrscheinlich ist, dass es zu einer Kollision kommt (das heißt, das Abschneiden sollte sicherer sein als bei einem Hex-Digest).

Hinweis: Verwenden von UUID4 (zufällig). Weitere Typen finden Sie unter http://en.wikipedia.org/wiki/Universally_unique_identifier .

JJ Geewax
quelle
7

Sie können einen vorhandenen Hash-Algorithmus verwenden, der etwas Kurzes erzeugt, wie MD5 (128 Bit) oder SHA1 (160). Dann können Sie dies weiter verkürzen, indem Sie Abschnitte des Digests mit anderen Abschnitten XOR-verknüpfen. Dies erhöht die Wahrscheinlichkeit von Kollisionen, ist jedoch nicht so schlimm wie das einfache Abschneiden des Digests.

Sie können auch die Länge der Originaldaten als Teil des Ergebnisses angeben, um sie eindeutiger zu machen. Zum Beispiel würde das XORing der ersten Hälfte eines MD5-Digests mit der zweiten Hälfte zu 64 Bit führen. Fügen Sie 32 Bit für die Länge der Daten hinzu (oder weniger, wenn Sie wissen, dass diese Länge immer in weniger Bits passt). Dies würde zu einem 96-Bit-Ergebnis (12 Byte) führen, das Sie dann in eine 24-stellige Hex-Zeichenfolge umwandeln könnten. Alternativ können Sie die Base 64-Codierung verwenden, um sie noch kürzer zu machen.

Dynamichael
quelle
2
FWIW, dies ist als XOR-Faltung bekannt.
PM 2Ring
7

Bei Bedarf können "sub-10-character hash" Sie den Fletcher-32- Algorithmus verwenden, der 8-Zeichen-Hash (32 Bit), CRC-32 oder Adler-32 erzeugt .

CRC-32 ist um den Faktor 20% - 100% langsamer als Adler32.

Fletcher-32 ist etwas zuverlässiger als Adler-32. Es hat einen geringeren Rechenaufwand als die Adler-Prüfsumme: Fletcher vs Adler-Vergleich .

Ein Beispielprogramm mit einigen Fletcher-Implementierungen ist unten angegeben:

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

Ausgabe:

4031760169 F04FC729                                                                                                                                                                                                                              
4031760169 F04FC729                                                                                                                                                                                                                              

1448095018 56502D2A                                                                                                                                                                                                                              
1448095018 56502D2A                                                                                                                                                                                                                              

Stimmt mit Testvektoren überein :

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

Adler-32 hat eine Schwäche für Kurznachrichten mit einigen hundert Bytes, da die Prüfsummen für diese Nachrichten eine schlechte Abdeckung der 32 verfügbaren Bits aufweisen. Überprüfen Sie dies:

Der Adler32-Algorithmus ist nicht komplex genug, um mit vergleichbaren Prüfsummen zu konkurrieren .

sg7
quelle
6

Führen Sie dies einfach in einem Terminal aus (unter MacOS oder Linux):

crc32 <(echo "some string")

8 Zeichen lang.

sgon00
quelle
4

Sie können die Hash- Bibliothek für Python verwenden. Die Shake_128- und Shake_256- Algorithmen bieten Hashes variabler Länge. Hier ist ein Arbeitscode (Python3):

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

Beachten Sie, dass die Funktion mit einem Längenparameter x (Beispiel 5) einen Hashwert der Länge 2x zurückgibt .

Feran
quelle
1

Es ist jetzt 2019 und es gibt bessere Möglichkeiten. Nämlich xxhash .

~ echo test | xxhsum                                                           
2d7f1808da1fa63c  stdin
Sorbet
quelle
Dieser Link ist defekt. Es ist besser, eine vollständigere Antwort zu geben.
eri0o
0

Ich brauchte in letzter Zeit etwas in der Art einer einfachen String-Reduktionsfunktion. Grundsätzlich sah der Code ungefähr so ​​aus (C / C ++ - Code voraus):

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

Es hat wahrscheinlich mehr Kollisionen als gewünscht, ist jedoch nicht für die Verwendung als kryptografische Hash-Funktion vorgesehen. Sie können verschiedene Multiplikatoren ausprobieren (dh die 37 in eine andere Primzahl ändern), wenn Sie zu viele Kollisionen erhalten. Eines der interessanten Merkmale dieses Snippets ist, dass Dest, wenn Src kürzer als Dest ist, die Eingabezeichenfolge unverändert erhält (0 * 37 + Wert = Wert). Wenn Sie am Ende des Prozesses etwas "Lesbares" wünschen, passt Normalize die transformierten Bytes auf Kosten zunehmender Kollisionen an.

Quelle:

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

CubicleSoft
quelle
std :: hash löst bestimmte Anwendungsfälle nicht (z. B. das Vermeiden des Aufziehens der aufgeblähten std :: -Vorlagen, wenn nur ein paar zusätzliche Codezeilen ausreichen). Hier ist nichts Dummes. Es wurde sorgfältig durchdacht, um die Hauptbeschränkungen in Mac OSX zu beseitigen. Ich wollte keine ganze Zahl. Dafür hätte ich djb2 verwenden und trotzdem vermeiden können, std :: templates zu verwenden.
CubicleSoft
Das klingt immer noch albern. Warum würden Sie jemals eine Verwendung von DestSizemehr als 4 (32 Bit) , wenn der Hash selbst so beschissen ist? Wenn Sie die Kollisionsbeständigkeit einer Ausgabe wünschen, die größer als ein int ist, würden Sie SHA verwenden.
Navin
Schau, es ist nicht wirklich ein traditioneller Hash. Es verfügt über nützliche Eigenschaften, bei denen der Benutzer die Zeichenfolgengröße an Stellen deklarieren kann, an denen unter bestimmten Betriebssystemen (z. B. Mac OSX) extrem begrenzter Pufferplatz vorhanden ist UND das Ergebnis in die begrenzte Domäne realer Dateinamen passen muss UND die nicht nur abgeschnitten werden sollen der Name, weil das Kollisionen verursachen würde (aber kürzere Zeichenfolgen werden in Ruhe gelassen). Ein kryptografischer Hash ist nicht immer die richtige Antwort und std :: hash ist auch nicht immer die richtige Antwort.
CubicleSoft