Funzione hash che produce hash brevi?


97

Esiste un metodo di crittografia che può richiedere una stringa di qualsiasi lunghezza e produrre un hash di meno di 10 caratteri? Voglio produrre ID ragionevolmente univoci ma basati sul contenuto del messaggio, piuttosto che in modo casuale.

Posso convivere vincolando i messaggi a valori interi, tuttavia, se le stringhe di lunghezza arbitraria sono impossibili. Tuttavia, l'hash non deve essere simile per due interi consecutivi, in quel caso.


Si chiama hash. Non sarà unico.
SLaks

1
Anche questo è un problema di troncamento dell'hash , quindi vedi anche stackoverflow.com/q/4784335
Peter Krauss

2
Cordiali saluti, vedere un elenco di funzioni hash in Wikipedia.
Basil Bourque

Risposte:


76

Puoi utilizzare qualsiasi algoritmo hash comunemente disponibile (ad es. SHA-1), che ti darà un risultato leggermente più lungo di quello che ti serve. Basta troncare il risultato alla lunghezza desiderata, che potrebbe essere abbastanza buona.

Ad esempio, in Python:

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

2
Qualsiasi funzione hash ragionevole può essere troncata.
Presidente James K. Polk

88
questo non aumenterebbe il rischio di collisione in misura molto maggiore?
Gabriel Sanmartin

143
@erasmospunk: la codifica con base64 non fa nulla per la resistenza alle collisioni, poiché se si hash(a)scontra con hash(b)allora base64(hash(a))si scontra anche con base64(hash(b)).
Greg Hewgill

56
@GregHewgill hai ragione, ma non stiamo parlando dell'algoritmo hash originale in collisione (sì, sha1collide ma questa è un'altra storia). Se hai un hash di 10 caratteri, ottieni un'entropia maggiore se è codificato con base64vs base16(o hex). Quanto più in alto? Con base16ottieni 4 bit di informazione per carattere, con base64questa cifra è 6 bit / carattere. In totale un hash "esadecimale" da 10 caratteri avrà 40 bit di entropia mentre un base64 60 bit. Quindi è leggermente più resistente, scusa se non sono stato super chiaro.
John L. Jegutanis

19
@erasmospunk: Oh capisco cosa intendi, sì, se hai una dimensione fissa limitata per il tuo risultato, puoi inserire bit più significativi con la codifica base64 rispetto alla codifica esadecimale.
Greg Hewgill

46

Se non hai bisogno di un algoritmo forte contro la modifica intenzionale, ho trovato un algoritmo chiamato adler32 che produce risultati piuttosto brevi (~ 8 caratteri). Sceglilo dal menu a discesa qui per provarlo:

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


2
è molto vecchio, non molto affidabile.
Mascarpone

1
@Mascarpone "poco affidabile" - fonte? Ha dei limiti, se li conosci non importa quanti anni abbia.
BT

8
@Mascarpone "meno punti deboli" - ancora una volta, quali punti deboli? Perché pensi che questo algoritmo non sia perfetto al 100% per l'utilizzo dell'OP?
BT

3
@Mascarpone L'OP non dice di volere un hash di qualità crittografica. OTOH, Adler32 è un checksum, non un hash, quindi potrebbe non essere adatto, a seconda di ciò che l'OP sta effettivamente facendo con esso.
PM 2 Ring,

2
C'è un avvertimento su Adler32, che cita Wikipedia : Adler-32 ha un debole per i messaggi brevi con poche centinaia di byte, perché i checksum per questi messaggi hanno una scarsa copertura dei 32 bit disponibili.
Basil Bourque

13

Devi eseguire l'hashing del contenuto per ottenere un digest. Sono disponibili molti hash ma 10 caratteri sono piuttosto piccoli per il set di risultati. Nel passato, le persone usavano CRC-32, che produce un hash a 33 bit (fondamentalmente 4 caratteri più un bit). C'è anche CRC-64 che produce un hash a 65 bit. MD5, che produce un hash a 128 bit (16 byte / caratteri) è considerato non funzionante ai fini crittografici perché è possibile trovare due messaggi che hanno lo stesso hash. Va da sé che ogni volta che crei un digest di 16 byte da un messaggio di lunghezza arbitraria, finirai con dei duplicati. Più breve è il digest, maggiore è il rischio di collisioni.

Tuttavia, la tua preoccupazione che l'hash non sia simile per due messaggi consecutivi (che siano interi o meno) dovrebbe essere vera con tutti gli hash. Anche un singolo cambiamento di bit nel messaggio originale dovrebbe produrre un digest risultante molto diverso.

Quindi, usando qualcosa come CRC-64 (e il risultato in base 64) dovrebbe portarti nel quartiere che stai cercando.


1
CRC'ing un hash SHA-1 e poi base-64 'il risultato rende l'ID risultante più resistente alla collisione?

5
"Tuttavia, la tua preoccupazione che l'hash non sia simile per due messaggi consecutivi [...] dovrebbe valere per tutti gli hash." - Non è necessariamente vero. Ad esempio, per le funzioni hash che vengono utilizzate per il clustering o il rilevamento dei cloni, in realtà è vero l'esatto contrario: si desidera che documenti simili producano valori hash simili (o addirittura uguali). Un noto esempio di algoritmo hash progettato specificamente per produrre valori identici per input simili è Soundex.
Jörg W Mittag

Sto usando gli hash per autenticare la firma del messaggio. Quindi, in pratica, per un messaggio noto e una firma specificata, l'hash deve essere corretto. Non mi importa se ci sarebbe una piccola percentuale di falsi positivi, però. È totalmente accettabile. Al momento utilizzo l'hash SHA-512 troncato compresso con base62 (qualcosa che ho creato rapidamente) per comodità.

@ JörgWMittag Eccellente punto su SoundEx. Mi correggo. Non tutti gli hash hanno le stesse caratteristiche.
Giovanni

12

Riassumendo solo una risposta che mi è stata utile (notando il commento di @erasmospunk sull'utilizzo della codifica base-64). Il mio obiettivo era quello di avere una stringa corta che fosse per lo più unica ...

Non sono un esperto, quindi correggilo se presenta errori evidenti (di nuovo in Python come la risposta accettata):

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

Il resultqui sta usando più di semplici caratteri esadecimali (quello che otterresti se lo usassi hash.hexdigest()) quindi è meno probabile che si verifichi una collisione (cioè, dovrebbe essere più sicuro troncare di un digest esadecimale).

Nota: utilizzo di UUID4 (casuale). Vedi http://en.wikipedia.org/wiki/Universally_unique_identifier per gli altri tipi.


7

È possibile utilizzare un algoritmo hash esistente che produce qualcosa di breve, come MD5 (128 bit) o ​​SHA1 (160). Quindi puoi accorciarlo ulteriormente XORing sezioni del digest con altre sezioni. Ciò aumenterà la possibilità di collisioni, ma non così grave come il semplice troncamento del digest.

Inoltre, potresti includere la lunghezza dei dati originali come parte del risultato per renderlo più unico. Ad esempio, XORing la prima metà di un digest MD5 con la seconda metà risulterebbe in 64 bit. Aggiungi 32 bit per la lunghezza dei dati (o un valore inferiore se sai che la lunghezza si adatterà sempre a meno bit). Ciò comporterebbe un risultato a 96 bit (12 byte) che potresti poi trasformare in una stringa esadecimale di 24 caratteri. In alternativa, puoi usare la codifica base 64 per renderlo ancora più breve.


2
FWIW, questo è noto come pieghevole XOR.
PM 2 Ring,

7

Se hai bisogno "sub-10-character hash" puoi usare l' algoritmo Fletcher-32 che produce 8 caratteri hash (32 bit), CRC-32 o Adler-32 .

CRC-32 è più lento di Adler32 di un fattore del 20% - 100%.

Fletcher-32 è leggermente più affidabile di Adler-32. Ha un costo computazionale inferiore rispetto al checksum di Adler: confronto tra Fletcher e Adler .

Di seguito viene fornito un programma di esempio con alcune implementazioni di 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;
    }

Produzione:

4031760169 F04FC729                                                                                                                                                                                                                              
4031760169 F04FC729                                                                                                                                                                                                                              

1448095018 56502D2A                                                                                                                                                                                                                              
1448095018 56502D2A                                                                                                                                                                                                                              

Concorda con i vettori di prova :

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

Adler-32 ha un debole per i messaggi brevi con poche centinaia di byte, perché i checksum per questi messaggi hanno una scarsa copertura dei 32 bit disponibili. Controllare questo:

L'algoritmo Adler32 non è abbastanza complesso da competere con checksum comparabili .


6

Eseguilo semplicemente in un terminale (su MacOS o Linux):

crc32 <(echo "some string")

8 caratteri di lunghezza.


4

Puoi usare la libreria hashlib per Python. Gli algoritmi shake_128 e shake_256 forniscono hash di lunghezza variabile. Ecco un po 'di codice funzionante (Python3):

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

Si noti che con un parametro di lunghezza x (5 nell'esempio) la funzione restituisce un valore hash di lunghezza 2x .


1

Ora è il 2019 e ci sono opzioni migliori. Vale a dire, xxhash .

~ echo test | xxhsum                                                           
2d7f1808da1fa63c  stdin

Questo collegamento è interrotto. è meglio fornire una risposta più completa.
eri0o

0

Di recente avevo bisogno di qualcosa sulla falsariga di una semplice funzione di riduzione delle stringhe. Fondamentalmente, il codice sembrava qualcosa del genere (codice C / C ++ in avanti):

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

Probabilmente ha più collisioni di quanto si potrebbe desiderare, ma non è inteso per l'uso come funzione di hash crittografica. Potresti provare vari moltiplicatori (ad esempio cambiare il 37 in un altro numero primo) se ottieni troppe collisioni. Una delle caratteristiche interessanti di questo frammento è che quando Src è più corto di Dest, Dest finisce con la stringa di input così com'è (0 * 37 + valore = valore). Se vuoi qualcosa di "leggibile" alla fine del processo, Normalize aggiusterà i byte trasformati a costo di aumentare le collisioni.

Fonte:

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


std :: hash non risolve alcuni casi d'uso (ad esempio evitando di trascinare il bloaty std :: templates quando saranno sufficienti solo poche righe di codice extra). Non c'è niente di stupido qui. È stato attentamente pensato per affrontare le principali limitazioni in Mac OSX. Non volevo un numero intero. Per questo, avrei potuto usare djb2 e comunque evitare di usare std :: templates.
CubicleSoft

Sembra ancora sciocco. Perché mai dovresti usare un valore DestSizemaggiore di 4 (32 bit) quando l'hash stesso è così schifoso? Se desideri la resistenza alle collisioni fornita da un'uscita più grande di un int, dovresti usare SHA.
Navin

Guarda, non è proprio un hashish tradizionale. Ha proprietà utili in cui l'utente può dichiarare la dimensione della stringa in luoghi in cui c'è uno spazio di buffer estremamente limitato su alcuni sistemi operativi (ad esempio Mac OSX) E il risultato deve rientrare nel dominio limitato dei nomi di file reali E non vogliono solo troncare il nome perché quello SAREBBE causare collisioni (ma le stringhe più corte vengono lasciate sole). Un hash crittografico non è sempre la risposta giusta e anche std :: hash non è sempre la risposta giusta.
CubicleSoft
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.