Hash una lista de cadenas de una manera resistente a la colisión

1

Tengo una lista de cadenas de las que necesito calcular el hash, pero no puedo averiguar cómo hacerlo de una manera que sea resistente a los ataques de colisión.

Por ejemplo, en este código de python:

def list_digest_1(strings):
    import hashlib
    hash = hashlib.sha1()
    for s in strings:
        hash.update(s)
    return hash.hexdigest()

Hay una colisión entre [b'foo', b'bar'] y [b'foobar'] .

Esto se puede reducir insertando un separador entre las cadenas:

def list_digest_2(strings):
    import hashlib
    hash = hashlib.sha1()
    for s in strings:
        hash.update(s)
        hash.update(b'
def list_digest_3(strings):
    import hashlib
    hash = hashlib.sha1()
    for s in strings:
        hash.update(
            hashlib.sha1(s).digest()
        )
    return hash.hexdigest()
') return hash.hexdigest()

Sin embargo, aún puedes crear fácilmente una colisión inyectando caracteres separadores en la cadena, por ejemplo. [b'foo[b'foo', b'bar%code%baz']bar', b'baz'] y %code% . Esto podría evitarse potencialmente eliminando las cadenas o escapando al carácter separador, pero preferiría hacerlo si es posible.

Otra posibilidad es agrupar cada cadena por separado y luego combinar los hashes:

def rand_str(length):
    return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(length)).encode('utf-8')

def rand_list(length, str_length):
    return [rand_str(length=str_length) for _ in range(length)]

import tqdm
str_list = [rand_list(length=10000, str_length=2) for _ in tqdm.tqdm(range(1000))]

for hash_fun in list_digest_1, list_digest_2, list_digest_3:
    t = timeit.Timer(lambda: [hash_fun(s) for s in str_list])
    print('{}: {}'.format(hash_fun.__name__, t.timeit(number=1)))

# list_digest_1: 1.318927247000829
# list_digest_2: 2.4033974090016272
# list_digest_3: 7.667939508999552

Tenga en cuenta que todavía no estoy seguro de si esto realmente resuelve el problema o simplemente lo mueve un paso atrás.

No estoy usando el hash para una tarea sensible a la seguridad, solo lo uso como un filtro preliminar para algunas consultas de la base de datos, para reducir el impacto en el rendimiento de las pruebas directas de igualdad en todo momento. Preferiría usar algo que sea resistente a este tipo de ataque (en teoría, un atacante podría inducir artificialmente una carga adicional al enviar colisiones o algo así), pero la tercera versión es significativamente peor cuando hay muchas cadenas pequeñas, lo que limita las razones de rendimiento para usar una función hash en primer lugar.

def list_digest_1(strings):
    import hashlib
    hash = hashlib.sha1()
    for s in strings:
        hash.update(s)
    return hash.hexdigest()

¿Cómo puedo evitar este problema al calcular el hash de una lista de cadenas? Además, si hay una herramienta Python existente que debería usar para esto, me encantaría saberlo.

    
pregunta AJMansfield 07.07.2017 - 20:31
fuente

3 respuestas

2

Puede utilizar la siguiente forma canónica de una matriz de cadenas:

<fixedLen1>string1><fixedLen2><string2>...

Implementación:

def list_digest(strings):
    import hashlib, struct
    hash = hashlib.sha1()
    for s in strings:
        hash.update(struct.pack("I", len(s)))
        hash.update(s)
    return hash.hexdigest()
    
respondido por el 40F4 07.07.2017 - 22:24
fuente
2

Para evitar ese tipo de colisión, es necesario que codifique la lista de cadenas de manera que, al menos conceptualmente, se pueda descodificar de forma inequívoca. Como muestra el caso "hash of hashes" (y, criptográficamente hablando, es un buen método), la palabra "conceptualmente" es un poco sutil.

De todos modos, veo dos métodos posibles que deberían lograr un rendimiento razonable:

  1. Use la técnica hash-of-hashes, con una función hash segura que es más rápida que SHA-1. Le sugiero que intente BLAKE2 (no el "hashing de árbol", solo BLAKE2b o BLAKE2s sin formato).

  2. Use una serialización personalizada. Un método simple sería agregar, como un prefijo a cada cadena, una codificación de su longitud; por ejemplo, codifique la longitud (en bytes) de la cadena exactamente, por ejemplo, 4 bytes (supongo aquí que ninguna cadena individual es más grande que 4 gigabytes). Es obvio que podría decodificar sin ambigüedad una lista codificada de este tipo. No tienes que implementar la decodificación; solo que podría hacerlo es suficiente para garantizar la protección contra colisiones.

Por supuesto, también puede hacer una serialización personalizada y intentar hacer hash con BLAKE2.

    
respondido por el Thomas Pornin 07.07.2017 - 22:29
fuente
1

Debe codificar de forma inequívoca la lista de cadenas en una secuencia de bytring. Por "no ambiguo" me refiero a que la función de codificación debe ser inyective ; cada entrada distinta debe asignarse a una salida distinta. Un buen tipo de caso de prueba unitaria para escribir aquí es escribir la función de codificación como una función independiente, independiente, escribir una función para decodificarla al original, y luego un caso de prueba que verifique que codificar-decodificar es una ronda viaje.

Este problema es similar a lo que los programadores denominan serialización , que se convierte entre un objeto en memoria y una representación de bytring que luego se puede deserializar para reconstruir el objeto original. Por lo tanto, las bibliotecas de serialización podrían ser útiles aquí, siempre que la salida serializada esté determinada consistentemente por la entrada. Lo que no siempre es el caso; por ejemplo, las bibliotecas de serialización JSON pueden producir múltiples salidas válidas para la misma entrada, dependiendo, por ejemplo, de sobre dónde eligen insertar o no espacios en blanco.

Un tipo muy simple de codificación que se usa a menudo en sistemas criptográficos es una codificación con prefijo de longitud, donde se imprime una lista de esta manera:

  1. Muestra la longitud de la lista, es decir, el número de elementos, como un campo de tamaño fijo (por ejemplo, un entero de 32 bits en orden de bytes little-endian);
  2. Para cada cadena de la lista:
    • Muestra la longitud de la cadena, también como un campo de tamaño fijo;
    • Genera los bytes en la cadena.
respondido por el Luis Casillas 07.07.2017 - 22:25
fuente

Lea otras preguntas en las etiquetas