¿Función hash que produce hashes cortos?

97

¿Existe alguna forma de cifrado que pueda tomar una cadena de cualquier longitud y producir un hash de menos de 10 caracteres? Quiero producir identificaciones razonablemente únicas pero basadas en el contenido del mensaje, en lugar de al azar.

Sin embargo, puedo vivir con la restricción de los mensajes a valores enteros, si las cadenas de longitud arbitraria son imposibles. Sin embargo, el hash no debe ser similar para dos enteros consecutivos, en ese caso.

rath3r
fuente
Eso se llama hash. No será único.
SLaks
1
Esto también es un problema de truncamiento de hash , así que consulte también stackoverflow.com/q/4784335
Peter Krauss
2
Para su información, consulte una lista de funciones hash en Wikipedia.
Basil Bourque

Respuestas:

76

Puede utilizar cualquier algoritmo hash comúnmente disponible (por ejemplo, SHA-1), que le dará un resultado un poco más largo de lo que necesita. Simplemente trunca el resultado a la longitud deseada, que puede ser lo suficientemente buena.

Por ejemplo, en Python:

>>> import hashlib
>>> hash = hashlib.sha1("my message".encode("UTF-8")).hexdigest()
>>> hash
'104ab42f1193c336aa2cf08a2c946d5c6fd0fcdb'
>>> hash[:10]
'104ab42f11'
Greg Hewgill
fuente
2
Cualquier función hash razonable se puede truncar.
Presidente James K. Polk
88
¿No aumentaría esto el riesgo de colisión en un grado mucho mayor?
Gabriel Sanmartin
143
@erasmospunk: codificar con base64 no hace nada para la resistencia a colisiones, ya que si hash(a)choca con hash(b), base64(hash(a))también choca con base64(hash(b)).
Greg Hewgill
56
@GregHewgill, tienes razón, pero no estamos hablando de la colisión del algoritmo hash original (sí, sha1colisiona , pero esta es otra historia). Si tiene un hash de 10 caracteres, obtendrá una entropía más alta si está codificado con base64vs base16(o hexadecimal). ¿Qué tan alto? Con base16obtienes 4 bits de información por carácter, con base64esta cifra es de 6 bits / char. En total, un hash "hexadecimal" de 10 caracteres tendrá 40 bits de entropía, mientras que una base64 de 60 bits. Entonces es un poco más resistente, perdón si no fui super claro.
John L. Jegutanis
19
@erasmospunk: Oh, ya veo lo que quieres decir, sí, si tienes un tamaño fijo limitado para tu resultado, puedes incluir bits más significativos con codificación base64 frente a codificación hexadecimal.
Greg Hewgill
46

Si no necesita un algoritmo que sea fuerte contra la modificación intencional, encontré un algoritmo llamado adler32 que produce resultados bastante cortos (~ 8 caracteres). Elíjalo del menú desplegable aquí para probarlo:

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

BT
fuente
2
es muy antiguo, no muy confiable.
Mascarpone
1
@Mascarpone "no muy confiable" - ¿fuente? Tiene limitaciones, si las conoces no importa la edad que tenga.
BT
8
@Mascarpone "menos debilidades" - de nuevo, ¿qué debilidades? ¿Por qué crees que este algoritmo no es 100% perfecto para el uso del OP?
BT
3
@Mascarpone El OP no dice que quieran un hash de grado criptográfico. OTOH, Adler32 es una suma de comprobación, no un hash, por lo que puede no ser adecuado, dependiendo de lo que el OP esté haciendo realmente con él.
PM 2 Ring
2
Hay una advertencia para Adler32, citando Wikipedia : Adler-32 tiene una debilidad para los mensajes cortos con unos pocos cientos de bytes, porque las sumas de comprobación para estos mensajes tienen una cobertura pobre de los 32 bits disponibles.
Basil Bourque
13

Necesita hash del contenido para obtener un resumen. Hay muchos hash disponibles, pero 10 caracteres es bastante pequeño para el conjunto de resultados. Hace mucho tiempo, la gente usaba CRC-32, que produce un hash de 33 bits (básicamente 4 caracteres más un bit). También existe CRC-64 que produce un hash de 65 bits. MD5, que produce un hash de 128 bits (16 bytes / caracteres) se considera roto para fines criptográficos porque se pueden encontrar dos mensajes que tienen el mismo hash. No hace falta decir que cada vez que cree un resumen de 16 bytes a partir de un mensaje de longitud arbitraria, terminará con duplicados. Cuanto más corto sea el resumen, mayor será el riesgo de colisiones.

Sin embargo, su preocupación de que el hash no sea similar para dos mensajes consecutivos (ya sean enteros o no) debería ser cierta con todos los hash. Incluso un cambio de un solo bit en el mensaje original debería producir un resumen resultante muy diferente.

Entonces, usar algo como CRC-64 (y base-64'ing el resultado) debería llevarlo al vecindario que está buscando.

Juan
fuente
1
¿CRC'ing un hash SHA-1 y luego base-64'ing el resultado hace que el ID resultante sea más resistente a la colisión?
5
"Sin embargo, su preocupación de que el hash no sea similar para dos mensajes consecutivos [...] debería ser cierta con todos los hash". - Eso no es necesariamente cierto. Por ejemplo, para las funciones hash que se utilizan para la detección de clústeres o clones, en realidad es exactamente lo contrario: desea que documentos similares produzcan valores hash similares (o incluso iguales). Un ejemplo bien conocido de algoritmo hash que está diseñado específicamente para producir valores idénticos para entradas similares es Soundex.
Jörg W Mittag
Estoy usando los hash para autenticar la firma del mensaje. Básicamente, para un mensaje conocido y una firma especificada, el hash debe ser correcto. Sin embargo, no me importa si habría un pequeño porcentaje de falsos positivos. Es totalmente aceptable. Actualmente uso el hash SHA-512 truncado comprimido con base62 (algo que preparé rápidamente) por conveniencia.
@ JörgWMittag Excelente punto en SoundEx. Me quedo corregido. No todos los hash tienen las mismas características.
John
12

Solo resumiendo una respuesta que fue útil para mí (señalando el comentario de @ erasmospunk sobre el uso de la codificación base-64). Mi objetivo era tener una cadena corta que en su mayoría fuera única ...

No soy un experto, así que corrija esto si tiene algún error evidente (en Python nuevamente como la respuesta aceptada):

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

El resultaquí está usando más que solo caracteres hexadecimales (lo que obtendría si los usara hash.hexdigest()) por lo que es menos probable que haya una colisión (es decir, debería ser más seguro truncar que un resumen hexadecimal).

Nota: Usando UUID4 (aleatorio). Consulte http://en.wikipedia.org/wiki/Universally_unique_identifier para los otros tipos.

JJ Geewax
fuente
7

Puede usar un algoritmo hash existente que produzca algo corto, como MD5 (128 bits) o SHA1 (160). Luego, puede acortarlo aún más mediante XORing de secciones del resumen con otras secciones. Esto aumentará la posibilidad de colisiones, pero no tan mal como simplemente truncar el resumen.

Además, puede incluir la longitud de los datos originales como parte del resultado para hacerlo más exclusivo. Por ejemplo, XORing de la primera mitad de un resumen MD5 con la segunda mitad daría como resultado 64 bits. Agregue 32 bits para la longitud de los datos (o menos si sabe que la longitud siempre cabrá en menos bits). Eso daría como resultado un resultado de 96 bits (12 bytes) que luego podría convertir en una cadena hexadecimal de 24 caracteres. Alternativamente, puede usar la codificación base 64 para hacerlo aún más corto.

Dynamichael
fuente
2
FWIW, esto se conoce como plegado XOR.
PM 2 Ring
7

Si lo necesita "sub-10-character hash" , puede usar el algoritmo Fletcher-32 que produce hash de 8 caracteres (32 bits), CRC-32 o Adler-32 .

CRC-32 es más lento que Adler32 en un factor de 20% a 100%.

Fletcher-32 es un poco más confiable que Adler-32. Tiene un costo computacional más bajo que la suma de comprobación de Adler: comparación de Fletcher vs Adler .

A continuación, se muestra un programa de muestra con algunas implementaciones de 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;
    }

Salida:

4031760169 F04FC729                                                                                                                                                                                                                              
4031760169 F04FC729                                                                                                                                                                                                                              

1448095018 56502D2A                                                                                                                                                                                                                              
1448095018 56502D2A                                                                                                                                                                                                                              

Está de acuerdo con los vectores de prueba :

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

Adler-32 tiene una debilidad para los mensajes cortos con unos pocos cientos de bytes, porque las sumas de comprobación para estos mensajes tienen una cobertura pobre de los 32 bits disponibles. Mira esto:

El algoritmo Adler32 no es lo suficientemente complejo como para competir con sumas de comprobación comparables .

sg7
fuente
6

Simplemente ejecute esto en una terminal (en MacOS o Linux):

crc32 <(echo "some string")

8 caracteres de largo.

sgon00
fuente
4

Puede usar la biblioteca hashlib para Python. Los algoritmos shake_128 y shake_256 proporcionan valores hash de longitud variable. Aquí hay un código de trabajo (Python3):

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

Observe que con un parámetro de longitud x (5 en el ejemplo) la función devuelve un valor hash de longitud 2x .

feran
fuente
1

Ahora es 2019 y hay mejores opciones. A saber, xxhash .

~ echo test | xxhsum                                                           
2d7f1808da1fa63c  stdin
sorbete
fuente
Este vínculo está roto. es mejor dar una respuesta más completa.
eri0o
0

Necesitaba algo parecido a una función de reducción de cadena simple recientemente. Básicamente, el código se parecía a esto (código C / C ++ más adelante):

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

Probablemente tenga más colisiones de las que se podrían desear, pero no está diseñado para usarse como función hash criptográfica. Puede probar varios multiplicadores (es decir, cambiar el 37 a otro número primo) si obtiene demasiadas colisiones. Una de las características interesantes de este fragmento es que cuando Src es más corto que Dest, Dest termina con la cadena de entrada tal cual (0 * 37 + valor = valor). Si desea algo "legible" al final del proceso, Normalize ajustará los bytes transformados a costa de aumentar las colisiones.

Fuente:

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

Cubículo Suave
fuente
std :: hash no resuelve ciertos casos de uso (por ejemplo, evitar arrastrar las plantillas std :: hinchadas cuando solo unas pocas líneas adicionales de código serán suficientes). No hay nada tonto aquí. Se pensó cuidadosamente para hacer frente a las principales limitaciones de Mac OSX. No quería un número entero. Para eso, podría haber usado djb2 y aun así evitar usar std :: templates.
CubicleSoft
Esto todavía suena tonto. ¿Por qué usted nunca utiliza un DestSizemayor de 4 (32 bits) cuando el hash en sí es tan cutre? Si quisiera la resistencia a colisiones proporcionada por una salida mayor que un int, usaría SHA.
Navin
Mira, no es realmente un hachís tradicional. Tiene propiedades útiles donde el usuario puede declarar el tamaño de la cadena en lugares donde hay un espacio de búfer extremadamente limitado en ciertos sistemas operativos (por ejemplo, Mac OSX) Y el resultado tiene que caber dentro del dominio limitado de los nombres de archivo reales Y no quieren simplemente truncar el nombre porque eso CAUSARÍA colisiones (pero las cadenas más cortas se dejan solas). Un hash criptográfico no siempre es la respuesta correcta y std :: hash tampoco siempre es la respuesta correcta.
CubicleSoft