Articulo de referencia

Función hash

Una función hash que asigna nombres a números enteros del 0 al 15. Existe una colisión entre las claves "John Smith" y "Sandra Dee". Una función hash es cualquier función que se...

Una función hash que asigna nombres a números enteros del 0 al 15. Existe una colisión entre las claves "John Smith" y "Sandra Dee".

Una función hash es cualquier función que se puede usar para mapear datos de tamaño arbitrario a valores de tamaño fijo, aunque hay algunas funciones hash que admiten una salida de longitud variable. [ 1 ] Los valores devueltos por una función hash se llaman valores hash , códigos hash , resúmenes ( hash/mensaje ) [ 2 ] o simplemente hashes . Los valores se suelen usar para indexar una tabla de tamaño fijo llamada tabla hash . El uso de una función hash para indexar una tabla hash se llama hashing o direccionamiento de almacenamiento disperso .

Las funciones hash y sus tablas hash asociadas se utilizan en aplicaciones de almacenamiento y recuperación de datos para acceder a ellos en un tiempo reducido y prácticamente constante por recuperación. Requieren un espacio de almacenamiento solo ligeramente superior al espacio total necesario para los datos o registros. El hashing es una forma de acceder a los datos de manera rápida y eficiente. A diferencia de las listas o los árboles, proporciona un tiempo de acceso casi constante. Además, utiliza mucho menos espacio de almacenamiento que intentar almacenar directamente todas las claves posibles, especialmente cuando estas son largas o de longitud variable.

El uso de funciones hash se basa en propiedades estadísticas de la interacción entre la clave y la función: el comportamiento en el peor de los casos es intolerablemente malo pero poco frecuente, y el comportamiento en el caso promedio puede ser casi óptimo ( colisión mínima ). [ 3 ] : 527

Las funciones hash están relacionadas con (y a menudo se confunden con) sumas de verificación , dígitos de control , huellas digitales , compresión con pérdida , funciones de aleatorización , códigos correctores de errores y cifrados . Si bien los conceptos se superponen en cierta medida, cada uno tiene sus propios usos y requisitos, y se diseña y optimiza de manera diferente. La función hash se diferencia de estos conceptos principalmente en términos de integridad de datos . Las tablas hash pueden usar funciones hash no criptográficas , mientras que las funciones hash criptográficas se utilizan en ciberseguridad para proteger datos confidenciales como contraseñas.

Descripción general

En una tabla hash, una función hash recibe como entrada una clave, la cual se asocia a un dato o registro y se utiliza para identificarlo en la aplicación de almacenamiento y recuperación de datos. Las claves pueden ser de longitud fija, como un número entero, o de longitud variable, como un nombre. En algunos casos, la clave es el propio dato. La salida es un código hash que se utiliza para indexar la tabla hash que contiene los datos o registros, o punteros a ellos.

Se puede considerar que una función hash realiza tres funciones:

  • Convierta claves de longitud variable en valores de longitud fija (generalmente de longitud de palabra de máquina o menos), plegándolas por palabras u otras unidades utilizando un operador que preserve la paridad como ADD o XOR,
  • Mezcla los bits de la clave de manera que los valores resultantes se distribuyan uniformemente en el espacio de claves y
  • Asigne valores clave a valores menores o iguales al tamaño de la tabla.

Una buena función hash satisface dos propiedades básicas: debe ser muy rápida de calcular y debe minimizar la duplicación de valores de salida ( colisiones ). Las funciones hash se basan en generar distribuciones de probabilidad favorables para su efectividad, reduciendo el tiempo de acceso a casi constante. Los altos factores de carga de la tabla, los conjuntos de claves patológicos y las funciones hash mal diseñadas pueden resultar en tiempos de acceso que se aproximan a lineales en el número de elementos en la tabla. Las funciones hash se pueden diseñar para proporcionar el mejor rendimiento en el peor de los casos, [ Notas 1 ] un buen rendimiento con altos factores de carga de la tabla y, en casos especiales, un mapeo perfecto (sin colisiones) de claves en códigos hash. La implementación se basa en operaciones de bits que preservan la paridad (XOR y ADD), multiplicación o división. Un complemento necesario para la función hash es un método de resolución de colisiones que emplea una estructura de datos auxiliar como listas enlazadas o una exploración sistemática de la tabla para encontrar una ranura vacía.

Tablas hash

Las funciones hash se utilizan junto con las tablas hash para almacenar y recuperar datos o registros. La función hash traduce la clave asociada a cada dato o registro en un código hash, que se utiliza para indexar la tabla hash. Cuando se añade un elemento a la tabla, el código hash puede indexar una ranura vacía (también llamada cubeta), en cuyo caso el elemento se añade allí. Si el código hash indexa una ranura llena, se requiere algún tipo de resolución de colisiones: el nuevo elemento puede omitirse (no añadirse a la tabla), reemplazar al elemento anterior o añadirse a la tabla en otra ubicación mediante un procedimiento específico. Dicho procedimiento depende de la estructura de la tabla hash. En el hash encadenado , cada ranura es la cabecera de una lista enlazada o cadena, y los elementos que colisionan en la ranura se añaden a la cadena. Las cadenas pueden mantenerse en orden aleatorio y buscarse linealmente, en orden secuencial o como una lista autoordenada por frecuencia para acelerar el acceso. En el hash de direcciones abiertas , la tabla se sondea comenzando desde la ranura ocupada de una manera específica, generalmente mediante sondeo lineal , sondeo cuadrático o doble hash, hasta que se localiza una ranura libre o se sondea toda la tabla (desbordamiento). La búsqueda del elemento sigue el mismo procedimiento hasta que se localiza el elemento, se encuentra una ranura libre o se ha buscado en toda la tabla (elemento no encontrado).

Usos especializados

Las funciones hash también se utilizan para crear cachés para grandes conjuntos de datos almacenados en medios lentos. Una caché suele ser más sencilla que una tabla de búsqueda hash, ya que cualquier colisión se puede resolver descartando o reescribiendo el elemento más antiguo de los dos elementos que colisionan. [ 4 ]

Las funciones hash son un ingrediente esencial del filtro de Bloom , una estructura de datos probabilística que utiliza espacio de forma eficiente y que se emplea para comprobar si un elemento pertenece a un conjunto .

Un caso especial de hashing se conoce como hashing geométrico o método de cuadrícula . En estas aplicaciones, el conjunto de todas las entradas es una especie de espacio métrico , y la función de hashing puede interpretarse como una partición de ese espacio en una cuadrícula de celdas . La tabla suele ser una matriz con dos o más índices (llamada archivo de cuadrícula , índice de cuadrícula , cuadrícula de cubetas , etc.), y la función de hashing devuelve una tupla de índice . Este principio se utiliza ampliamente en gráficos por computadora , geometría computacional y muchas otras disciplinas para resolver muchos problemas de proximidad en el plano o en el espacio tridimensional , como encontrar pares más cercanos en un conjunto de puntos, formas similares en una lista de formas, imágenes similares en una base de datos de imágenes , etc.

Las tablas hash también se utilizan para implementar arreglos asociativos y conjuntos dinámicos . [ 5 ]

Propiedades

Uniformidad

Una buena función hash debe distribuir las entradas esperadas de la forma más uniforme posible en su rango de salida. Es decir, cada valor hash en el rango de salida debe generarse con una probabilidad aproximadamente igual . El motivo de este requisito es que el coste de los métodos basados ​​en hash aumenta considerablemente a medida que se incrementa el número de colisiones (pares de entradas que se asignan al mismo valor hash). Si algunos valores hash tienen mayor probabilidad de ocurrir que otros, una mayor proporción de las operaciones de búsqueda tendrá que examinar un conjunto mayor de entradas de la tabla que colisionan.

Este criterio solo requiere que el valor tenga una distribución uniforme , no que sea aleatorio en ningún sentido. Una buena función de aleatorización suele ser una buena opción como función hash (salvo problemas de eficiencia computacional), pero lo contrario no tiene por qué ser cierto.

Las tablas hash suelen contener solo un pequeño subconjunto de las entradas válidas. Por ejemplo, una lista de miembros de un club puede contener solo un centenar de nombres, de un conjunto muy grande de todos los nombres posibles. En estos casos, el criterio de uniformidad debe cumplirse para casi todos los subconjuntos típicos de entradas que se puedan encontrar en la tabla, no solo para el conjunto global de todas las entradas posibles.

En otras palabras, si un conjunto típico de m registros se asigna mediante hash a n ranuras de tabla, la probabilidad de que un cubo reciba muchos más de m / n registros debería ser prácticamente nula. En particular, si m < n , muy pocos cubos deberían tener más de uno o dos registros. Un pequeño número de colisiones es prácticamente inevitable, incluso si n es mucho mayor que m (véase el problema del cumpleaños ).

En casos especiales, cuando las claves se conocen de antemano y el conjunto de claves es estático, se puede encontrar una función hash que logre una uniformidad absoluta (o sin colisiones). Dicha función hash se denomina perfecta . No existe un método algorítmico para construir tal función; su búsqueda es una función factorial del número de claves a mapear frente al número de ranuras de la tabla en las que se mapean. Encontrar una función hash perfecta sobre un conjunto de claves mayor que uno muy pequeño suele ser computacionalmente inviable; la función resultante probablemente sea computacionalmente más compleja que una función hash estándar y solo proporciona una ventaja marginal sobre una función con buenas propiedades estadísticas que produce un número mínimo de colisiones. Véase función hash universal .

Pruebas y mediciones

Al probar una función hash, la uniformidad de la distribución de los valores hash se puede evaluar mediante la prueba de chi-cuadrado . Esta prueba es una medida de bondad de ajuste: es la distribución real de los elementos en los cubos frente a la distribución esperada (o uniforme) de los elementos. La fórmula es [ 6 ].

metronortej=1metrobj2norte{\displaystyle {\frac {m}{n}}{\sum _{j=1}^{m}b_{j}^{2}}-n}

donde n es el número de claves, m es el número de cubetas y b j es el número de elementos en la cubeta j .

Una proporción dentro de un intervalo de confianza (como de 0,95 a 1,05) indica que la función hash evaluada tiene una distribución uniforme esperada.

Las funciones hash pueden tener ciertas propiedades técnicas que aumentan la probabilidad de que tengan una distribución uniforme al aplicarse. Una de ellas es el criterio de avalancha estricta : cada vez que se complementa un bit de entrada, cada uno de los bits de salida cambia con una probabilidad del 50 %. La razón de esta propiedad es que subconjuntos seleccionados del espacio de claves pueden tener baja variabilidad. Para que la salida se distribuya uniformemente, una baja variabilidad, incluso de un solo bit, debería traducirse en una alta variabilidad (es decir, distribución en el espacio de tablas) en la salida. Cada bit debería cambiar con una probabilidad del 50 % porque, si algunos bits son reacios a cambiar, las claves se agrupan alrededor de esos valores. Si los bits tienden a cambiar con demasiada facilidad, el mapeo se aproxima a una función XOR fija de un solo bit. En la literatura se han descrito pruebas estándar para esta propiedad. [ 7 ] Aquí se evalúa la relevancia del criterio para una función hash multiplicativa. [ 8 ]

Eficiencia

En las aplicaciones de almacenamiento y recuperación de datos, el uso de una función hash implica un compromiso entre el tiempo de búsqueda y el espacio de almacenamiento. Si el tiempo de búsqueda fuera ilimitado, una lista lineal no ordenada muy compacta sería el medio óptimo; si el espacio de almacenamiento fuera ilimitado, una estructura de acceso aleatorio indexable por clave-valor sería muy grande y dispersa, pero muy rápida. Una función hash requiere un tiempo finito para mapear un espacio de claves potencialmente grande a un espacio de almacenamiento factible, que se puede buscar en un tiempo limitado, independientemente del número de claves. En la mayoría de las aplicaciones, la función hash debe poder calcularse con una latencia mínima y, secundariamente, con un número mínimo de instrucciones.

La complejidad computacional varía según el número de instrucciones requeridas y la latencia de cada instrucción individual. Los métodos más sencillos son los basados ​​en bits (plegado), seguidos de los métodos multiplicativos, y los más complejos (más lentos) son los basados ​​en la división.

Dado que las colisiones deberían ser poco frecuentes y causar un retraso mínimo, pero por lo demás inofensivas, generalmente es preferible elegir una función hash más rápida en lugar de una que requiera más cálculos pero que evite algunas colisiones.

Las implementaciones basadas en la división pueden ser particularmente problemáticas, ya que una división requiere múltiples ciclos en casi todas las microarquitecturas de procesadores . La división ( módulo ) por una constante se puede invertir para convertirse en una multiplicación por el inverso multiplicativo de tamaño de palabra de esa constante. Esto lo puede hacer el programador o el compilador . La división también se puede reducir directamente a una serie de restas y sumas con desplazamiento, aunque minimizar el número de tales operaciones requeridas es un problema complejo; el número de instrucciones en lenguaje máquina resultantes puede superar la docena y saturar la tubería. Si la microarquitectura cuenta con unidades funcionales de multiplicación de hardware , entonces la multiplicación por el inverso probablemente sea un mejor enfoque.

Podemos permitir que el tamaño de la tabla n no sea una potencia de 2 y aun así no tener que realizar ninguna operación de resto o división, ya que estos cálculos a veces son costosos. Por ejemplo, sea n significativamente menor que 2 b . Consideremos una función generadora de números pseudoaleatorios P ​​(clave) que es uniforme en el intervalo [0, 2 b  1] . Una función hash uniforme en el intervalo [0, n 1] es n P (clave) / 2 b . Podemos reemplazar la división por un desplazamiento de bits a la derecha (posiblemente más rápido) : n P (clave) >> b .

Si las claves se someten a un proceso de hash repetido y la función hash es costosa, se puede ahorrar tiempo de cálculo precalculando los códigos hash y almacenándolos junto con las claves. Si los códigos hash coinciden, casi con toda seguridad las claves son idénticas. Esta técnica se utiliza en la tabla de transposición de los programas de juegos, que almacena una representación hash de 64 bits de la posición del tablero.

Universalidad

Un esquema de hash universal es un algoritmo aleatorio que selecciona una función hash h de entre una familia de funciones de este tipo, de manera que la probabilidad de colisión entre dos claves distintas cualesquiera sea 1/ m , donde m es el número de valores hash distintos deseados, independientemente de las dos claves. El hash universal garantiza (en un sentido probabilístico) que la aplicación de la función hash se comportará tan bien como si utilizara una función aleatoria, para cualquier distribución de los datos de entrada. Sin embargo, tendrá más colisiones que el hash perfecto y puede requerir más operaciones que una función hash de propósito específico.

Aplicabilidad

Una función hash que solo permite ciertos tamaños de tabla o cadenas de hasta cierta longitud, o que no puede aceptar una semilla (es decir, permite el doble hash) es menos útil que una que sí lo permite.

Una función hash es aplicable en diversas situaciones. Particularmente dentro de la criptografía, las aplicaciones notables incluyen: [ 9 ]

  • Verificación de integridad : Los valores hash idénticos para diferentes archivos implican igualdad, lo que proporciona un medio fiable para detectar modificaciones en los archivos.
  • Derivación de claves : Pequeños cambios en la entrada dan como resultado una alteración de la salida que parece aleatoria, conocida como propiedad de difusión. Por lo tanto, las funciones hash son valiosas para las funciones de derivación de claves.
  • Códigos de autenticación de mensajes (MAC): Mediante la integración de una clave confidencial con los datos de entrada, las funciones hash pueden generar MAC que garanticen la autenticidad de los datos, como en el caso de los HMAC .
  • Almacenamiento de contraseñas: El valor hash de la contraseña no revela ningún detalle de la misma, lo que subraya la importancia de almacenar de forma segura las contraseñas cifradas en el servidor.
  • Firmas : Se firman los hashes de los mensajes en lugar del mensaje completo.

Determinista

Un procedimiento de hash debe ser determinista : para un valor de entrada dado, siempre debe generar el mismo valor hash. En otras palabras, debe ser una función de los datos que se van a hashear, en el sentido matemático del término. Este requisito excluye las funciones hash que dependen de parámetros variables externos, como generadores de números pseudoaleatorios o la hora del día. También excluye las funciones que dependen de la dirección de memoria del objeto que se está hasheando, ya que la dirección puede cambiar durante la ejecución (como puede ocurrir en sistemas que utilizan ciertos métodos de recolección de basura ), aunque a veces es posible volver a hashear el elemento.

El determinismo se da en el contexto de la reutilización de la función. Por ejemplo, Python añade la característica de que las funciones hash utilizan una semilla aleatoria que se genera una sola vez al iniciar el proceso de Python, además de la entrada que se va a hashear. [ 10 ] La función hash de Python ( SipHash ) sigue siendo una función hash válida cuando se utiliza dentro de una sola ejecución, pero si los valores se persisten (por ejemplo, se escriben en el disco), ya no se pueden tratar como valores hash válidos, puesto que en la siguiente ejecución el valor aleatorio podría ser diferente.

Rango definido

A menudo es deseable que la salida de una función hash tenga un tamaño fijo (pero véase más adelante). Si, por ejemplo, la salida está restringida a valores enteros de 32 bits, los valores hash se pueden usar para indexar una matriz. Este tipo de hash se usa comúnmente para acelerar las búsquedas de datos. [ 11 ] Se puede producir una salida de longitud fija a partir de una entrada de longitud variable dividiendo los datos de entrada en fragmentos de tamaño específico. Las funciones hash utilizadas para búsquedas de datos usan alguna expresión aritmética que procesa iterativamente fragmentos de la entrada (como los caracteres de una cadena) para producir el valor hash. [ 11 ]

Rango variable

En muchas aplicaciones, el rango de valores hash puede variar en cada ejecución del programa o cambiar durante la misma ejecución (por ejemplo, cuando se necesita expandir una tabla hash). En esos casos, se requiere una función hash que acepte dos parámetros: los datos de entrada z y el número n de valores hash permitidos.

Una solución común consiste en calcular una función hash fija con un rango muy amplio (por ejemplo, de 0 a 2³² − 1   ), dividir el resultado por n y utilizar el resto de la división . Si n es una potencia de 2 , esto se puede lograr mediante enmascaramiento y desplazamiento de bits . Al utilizar este método, la función hash debe elegirse de manera que el resultado tenga una distribución bastante uniforme entre 0 y n  1 , para cualquier valor de n que pueda aparecer en la aplicación. Dependiendo de la función, el resto puede ser uniforme solo para ciertos valores de n , por ejemplo, números impares o primos .

Rango variable con movimiento mínimo (función hash dinámica)

Cuando se utiliza la función hash para almacenar valores en una tabla hash que perdura más allá de la ejecución del programa, y ​​es necesario ampliar o reducir la tabla hash, se la denomina tabla hash dinámica.

Es deseable una función hash que reubique el número mínimo de registros cuando se redimensiona la tabla. Lo que se necesita es una función hash H ( z , n ) (donde z es la clave que se está hasheando y n es el número de valores hash permitidos) tal que H ( z , n  +  1) = H ( z , n ) con una probabilidad cercana a n /( n  +  1) .

El hash lineal y el hash espiral son ejemplos de funciones hash dinámicas que se ejecutan en tiempo constante, pero que relajan la propiedad de uniformidad para lograr la propiedad de movimiento mínimo. El hash extensible utiliza una función hash dinámica que requiere un espacio proporcional a n para calcular la función hash, y esta se convierte en una función de las claves previamente insertadas. Se han inventado varios algoritmos que preservan la propiedad de uniformidad, pero que requieren un tiempo proporcional a n para calcular el valor de H ( z , n ) .

Una función hash con movimiento mínimo es especialmente útil en tablas hash distribuidas .

Normalización de datos

En algunas aplicaciones, los datos de entrada pueden contener características irrelevantes para la comparación. Por ejemplo, al buscar un nombre, puede ser conveniente ignorar la distinción entre mayúsculas y minúsculas. Para este tipo de datos, se debe utilizar una función hash compatible con el criterio de equivalencia de datos empleado: es decir, dos entradas consideradas equivalentes deben generar el mismo valor hash. Esto se puede lograr normalizando la entrada antes de aplicar la función hash, por ejemplo, convirtiendo todas las letras a mayúsculas.

Hashing de tipos de datos enteros

Existen varios algoritmos comunes para el hash de números enteros. El método que proporciona la mejor distribución depende de los datos. Uno de los métodos más sencillos y comunes en la práctica es el método de división modular.

Función hash de identidad

Si los datos a procesar son lo suficientemente pequeños, se pueden usar los datos mismos (reinterpretados como un número entero) como valor hash. El costo de calcular esta función hash de identidad es prácticamente cero. Esta función hash es perfecta , ya que asigna a cada entrada un valor hash distinto.

El significado de "suficientemente pequeño" depende del tamaño del tipo de dato utilizado como valor hash. Por ejemplo, en Java , el código hash es un entero de 32 bits. Por lo tanto, los objetos enteros Integery de punto flotante de 32 bits Floatpueden usar el valor directamente, mientras que los enteros Longy de punto flotante de 64 bits Doubleno pueden.

Otros tipos de datos también pueden usar este esquema de hash. Por ejemplo, al mapear cadenas de caracteres entre mayúsculas y minúsculas , se puede usar la codificación binaria de cada carácter, interpretada como un entero, para indexar una tabla que da la forma alternativa de ese carácter ("A" para "a", "8" para "8", etc.). Si cada carácter se almacena en 8 bits (como en ASCII extendido [ Notas 2 ] o latín ISO 1 ), la tabla tiene solo 2⁸ = 256 entradas; en el caso de caracteres Unicode , la tabla tendría 17 × 2¹⁶ =1 114 112 entradas.

La misma técnica se puede utilizar para asignar códigos de país de dos letras como "us" o "za" a nombres de países (26 2 = 676 entradas de tabla), códigos postales de 5 dígitos como 13083 a nombres de ciudades (100 000 entradas), etc. Los valores de datos no válidos (como el código de país "xx" o el código postal 00000) pueden dejarse sin definir en la tabla o asignarse a algún valor "nulo" apropiado.

Función hash trivial

Si las claves están distribuidas de manera uniforme o suficientemente uniforme en el espacio de claves, de modo que los valores de las claves sean esencialmente aleatorios, entonces se puede considerar que ya están "hasheadas". En este caso, cualquier cantidad de bits cualesquiera de la clave se puede extraer y cotejar como un índice en la tabla hash. Por ejemplo, una función hash simple podría enmascarar los m bits menos significativos y usar el resultado como un índice en una tabla hash de tamaño 2 m .

Cuadrados centrales

Un código hash de cuadrados medios se produce elevando al cuadrado la entrada y extrayendo un número apropiado de dígitos o bits centrales. Por ejemplo, si la entrada es123 456 789 y el tamaño de la tabla hash10 000 , luego elevar al cuadrado la clave produce15 241 578 750 190 521 , por lo que el código hash se toma como los 4 dígitos centrales del número de 17 dígitos (ignorando el dígito más alto) 8750. El método de cuadrados centrales produce un código hash razonable si no hay muchos ceros iniciales o finales en la clave. Esta es una variante del hash multiplicativo, pero no es tan buena porque una clave arbitraria no es un buen multiplicador.

Hash de división

Una técnica estándar consiste en utilizar una función módulo sobre la clave, seleccionando un divisor M que sea un número primo cercano al tamaño de la tabla, de modo que h ( K ) K (mod M ) . El tamaño de la tabla suele ser una potencia de 2. Esto proporciona una distribución de {0, M 1} . Esto ofrece buenos resultados sobre un gran número de conjuntos de claves. Una desventaja significativa del hash por división es que la división requiere múltiples ciclos en la mayoría de las arquitecturas modernas (incluida x86 ) y puede ser 10 veces más lenta que la multiplicación. Otra desventaja es que no separará las claves agrupadas. Por ejemplo, las claves 123000, 456000, 789000, etc., módulo 1000, se asignan a la misma dirección. Esta técnica funciona bien en la práctica porque muchos conjuntos de claves ya son suficientemente aleatorios, y la probabilidad de que un conjunto de claves sea cíclico por un número primo grande es pequeña.

Codificación algebraica

La codificación algebraica es una variante del método de división de hash que utiliza la división por un polinomio módulo 2 en lugar de un entero para mapear n bits a m bits. [ 3 ] : 512–513 En este enfoque, M = 2 m , y postulamos un polinomio de grado m Z ( x ) = x m + ζ m 1 x m 1 + + ζ 0 . Una clave K = ( k n 1 k 1 k 0 ) 2 puede considerarse como el polinomio K ( x ) = k n 1 x n 1 + + k 1 x + k 0 . El resto usando aritmética polinomial módulo 2 es K ( x ) mod Z ( x ) = h m 1 x m 1 + h 1 x + h 0 . Entonces h ( K ) = ( h m 1 h 1 h 0 ) 2 . Si Z ( x ) se construye para tener t o menos coeficientes distintos de cero, entonces las claves que comparten menos de t bits tienen garantizado que no colisionarán.

Z es una función de k , t y n (el último de los cuales es un divisor de 2k 1 ) y se construye a partir del cuerpo finito GF(2k ) . Knuth da un ejemplo: tomando ( n , m , t ) = (15, 10, 7) se obtiene Z ( x ) = x10 + x8 + x5 + x4 + x2 + x + 1 . La derivación es la siguiente :

Sea S el conjunto más pequeño de enteros tal que {1,2, , t } S y (2 j mod n ) S j S . [ Notas 3 ]

DefinirPAG(incógnita)=jS(incógnitaαj){\displaystyle P(x)=\prod _{j\in S}(x-\alpha ^{j})}donde α n GF(2 k ) y donde los coeficientes de P ( x ) se calculan en este campo. Entonces el grado de P ( x ) = | S | . Dado que α 2 j es una raíz de P ( x ) siempre que α j sea una raíz, se deduce que los coeficientes p i de P ( x ) satisfacen p 2 i = p i , por lo que todos son 0 o 1. Si R ( x ) = r n 1 x n 1 + + r 1 x + r 0 es cualquier polinomio no nulo módulo 2 con como máximo t coeficientes no nulos, entonces R ( x ) no es un múltiplo de P ( x ) módulo 2. [ Notas 4 ] Si se deduce que la función hash correspondiente asignará claves con menos de t bits en común a índices únicos. [ 3 ] : 542–543

El resultado habitual es que, para que el esquema sea computacionalmente factible, n o t aumenten considerablemente, o ambos. Por lo tanto, es más adecuado para su implementación en hardware o microcódigo. [ 3 ] : 542–543

Hash de permutación única

El hash de permutación única tiene garantizado un tiempo de inserción en el peor de los casos óptimo. [ 12 ]

Hash multiplicativo

El hash multiplicativo estándar utiliza la fórmula h a ( K ) = ( aK mod W ) / ( W / M ) , que produce un valor hash en {0, , M 1} . El valor a es un valor elegido apropiadamente que debe ser relativamente primo con W ; debe ser grande y su representación binaria una mezcla aleatoria de 1s y 0s. Un caso especial práctico importante ocurre cuando W = 2 w y M = 2 m son potencias de 2 y w es el tamaño de palabra de la máquina . En este caso, esta fórmula se convierte en h a ( K ) = ( aK mod 2 w ) / 2 w m . Esto es especial porque la aritmética módulo 2 w se realiza por defecto en los lenguajes de programación de bajo nivel y la división entera por una potencia de 2 es simplemente un desplazamiento a la derecha, por lo que, en C , por ejemplo, esta función se convierte en

hash sin signo ( K ​​sin signo ) { return ( a * K ) >> ( w - m ); }

y para valores fijos de m y w, esto se traduce en una sola multiplicación entera y un desplazamiento a la derecha, lo que la convierte en una de las funciones hash más rápidas de calcular.

El hash multiplicativo es susceptible a un "error común" que conduce a una difusión deficiente : los bits de entrada de mayor valor no afectan a los bits de salida de menor valor. [ 13 ] Una transmutación en la entrada que desplaza el rango de los bits superiores retenidos hacia abajo y los combina mediante XOR o ADD a la clave antes del paso de multiplicación corrige esto. La función resultante se ve así: [ 8 ]

hash sin signo ( K ​​sin signo ) { K ^= K >> ( w - m ); return ( a * K ) >> ( w - m ); }

Hash de Fibonacci

El hash de Fibonacci es una forma de hash multiplicativo en la que el multiplicador es 2w / ϕ , donde w es la longitud de la palabra de la máquina y ϕ (phi) es la proporción áurea (aproximadamente 1,618). Una propiedad de este multiplicador es que distribuye uniformemente sobre el espacio de la tabla bloques de claves consecutivas con respecto a cualquier bloque de bits en la clave. Las claves consecutivas dentro de los bits altos o bajos de la clave (o algún otro campo) son relativamente comunes. Los multiplicadores para varias longitudes de palabra son:

  • 16: a = 9E37 16 =40 503 10
  • 32: a = 9E37 79B9 16 =2 654 435 769 10
  • 48: a = 9E37 79B9 7F4B 16 =173 961 102 589 771 10 [ Notas 5 ]
  • 64: a = 9E37 79B9 7F4A 7C15 16 =11 400 714 819 323 198 485 10

El multiplicador debe ser impar, de modo que el bit menos significativo de la salida sea invertible módulo 2w . Los dos últimos valores dados anteriormente se redondean (hacia arriba y hacia abajo, respectivamente) en más de la mitad de un bit menos significativo para lograr esto.

Hashing de Zobrist

El hash de Zobrist , que recibe su nombre de Albert Zobrist , es una forma de hash tabulado , un método para construir familias universales de funciones hash combinando la búsqueda en tablas con operaciones XOR. Este algoritmo ha demostrado ser muy rápido y de alta calidad para fines de hash (especialmente para el hash de claves numéricas enteras). [ 14 ]

El hash Zobrist se introdujo originalmente como un método para representar de forma compacta las posiciones de ajedrez en programas de juegos de ordenador. Se asignaba un número aleatorio único a cada tipo de pieza (seis para las blancas y seis para las negras) en cada casilla del tablero. Así, al inicio del programa, se inicializaba una tabla de 64 × 12 de estos números. Los números aleatorios podían tener cualquier longitud, pero 64 bits era lo más adecuado debido a las 64 casillas del tablero. Una posición se transcribía recorriendo las piezas, indexando los números aleatorios correspondientes (las casillas vacías no se incluían en el cálculo) y aplicando la operación XOR (el valor inicial podía ser 0, el valor neutro de XOR, o una semilla aleatoria ). El valor resultante se reducía mediante módulo, plegado u otra operación para generar un índice en la tabla hash. El hash Zobrist original se almacenaba en la tabla como la representación de la posición.

Posteriormente, el método se extendió al hash de enteros representando cada byte en cada una de las 4 posiciones posibles de la palabra con un número aleatorio único de 32 bits. De esta forma, se construye una tabla de 2⁸ × 4 números aleatorios. Un entero hash de 32 bits se transcribe indexando sucesivamente la tabla con el valor de cada byte del entero en texto plano y aplicando la operación XOR a los valores cargados (el valor inicial puede ser el valor de identidad o una semilla aleatoria). La extensión natural a enteros de 64 bits se realiza mediante una tabla de 2⁸ × 8 números aleatorios de 64 bits.

Este tipo de función tiene algunas propiedades teóricas interesantes, una de las cuales se llama independencia de 3-tuplas , lo que significa que cualquier 3-tupla de claves tiene la misma probabilidad de ser asignada a cualquier 3-tupla de valores hash.

Función hash personalizada

Se puede diseñar una función hash para aprovechar la entropía presente en las claves. Si las claves tienen ceros iniciales o finales, o campos específicos que no se utilizan, que siempre son cero o constantes, o que varían poco, entonces enmascarar solo los bits volátiles y aplicarles el hash proporcionará una función hash mejor y posiblemente más rápida. La selección de divisores o multiplicadores en los esquemas de división y multiplicación puede generar funciones hash más uniformes si las claves son cíclicas o presentan otras redundancias.

Hashing de datos de longitud variable

Cuando los datos son cadenas de caracteres largas (o de longitud variable) , como nombres propios, direcciones web o mensajes de correo electrónico, su distribución suele ser muy desigual, con dependencias complejas. Por ejemplo, el texto en cualquier idioma natural presenta distribuciones de caracteres y pares de caracteres muy no uniformes , características propias del idioma. Para este tipo de datos, es recomendable utilizar una función hash que dependa de todos los caracteres de la cadena y que, además, dependa de cada carácter de forma diferente.

Medio y extremos

Las funciones hash simples pueden sumar los primeros y los últimos n caracteres de una cadena junto con su longitud, o generar un hash del tamaño de una palabra a partir de los 4 caracteres centrales de la cadena. Esto evita iterar sobre la cadena (potencialmente larga), pero las funciones hash que no aplican hash a todos los caracteres de una cadena pueden volverse lineales fácilmente debido a redundancias, agrupamiento u otras anomalías en el conjunto de claves. Estas estrategias pueden ser efectivas como función hash personalizada si la estructura de las claves es tal que los campos centrales, finales u otros campos son cero o alguna otra constante invariante que no diferencia las claves; entonces, las partes invariantes de las claves pueden ignorarse.

Plegado de caracteres

El ejemplo paradigmático de plegado por caracteres consiste en sumar los valores enteros de todos los caracteres de la cadena. Una mejor idea es multiplicar el total del hash por una constante, normalmente un número primo considerable, antes de añadir el siguiente carácter, ignorando el desbordamiento. Utilizar la operación OR exclusiva en lugar de la suma también es una alternativa plausible. La operación final sería un módulo, una máscara u otra función para reducir el valor de la palabra a un índice del tamaño de la tabla. La debilidad de este procedimiento es que la información puede agruparse en los bits superiores o inferiores de los bytes; esta agrupación permanecerá en el resultado del hash y provocará más colisiones que un hash aleatorio adecuado. Los códigos de bytes ASCII, por ejemplo, tienen un bit superior de 0, y las cadenas imprimibles no utilizan el último código de byte ni la mayoría de los primeros 32 códigos de bytes, por lo que la información, que utiliza los códigos de bytes restantes, se agrupa en los bits restantes de una manera poco evidente.

El enfoque clásico, denominado hash PJW basado en el trabajo de Peter J. Weinberger en Bell Labs en la década de 1970, fue diseñado originalmente para generar hashes de identificadores en tablas de símbolos del compilador, como se indica en el "Libro del Dragón" . [ 15 ] Esta función hash desplaza los bytes 4 bits antes de sumarlos. Cuando la cantidad se reinicia, los 4 bits superiores se desplazan hacia afuera y, si no son cero, se combinan mediante XOR con el byte inferior de la cantidad acumulada. El resultado es un código hash de tamaño de palabra al que se le puede aplicar un módulo u otra operación de reducción para producir el índice hash final.

Hoy en día, especialmente con la llegada de los tamaños de palabra de 64 bits, se dispone de un método mucho más eficiente de hash de cadenas de longitud variable mediante fragmentos de palabras.

Plegado de longitud de palabra

Los microprocesadores modernos permiten un procesamiento mucho más rápido si las cadenas de caracteres de 8 bits no se procesan carácter por carácter, sino que se interpretan como una matriz de enteros de 32 o 64 bits y se acumulan estos valores enteros de "palabra ancha" mediante operaciones aritméticas (por ejemplo, multiplicación por una constante y desplazamiento de bits). La palabra final, que puede tener posiciones de bytes sin ocupar, se rellena con ceros o un valor aleatorio específico antes de incorporarse al hash. El código hash acumulado se reduce mediante una operación de módulo u otra operación para generar un índice en la tabla.

Hashing de conversión de radix

Análogamente a cómo una cadena de caracteres ASCII o EBCDIC que representa un número decimal se convierte en una cantidad numérica para su cálculo, una cadena de longitud variable se puede convertir como x k 1 a k −1 + x k 2 a k −2 + + x 1 a + x 0 . Esto es simplemente un polinomio en una base a > 1   que toma los componentes ( x 0 , x 1 ,..., x k −1 ) como los caracteres de la cadena de entrada de longitud k . Se puede usar directamente como código hash, o una función hash aplicada para mapear el valor potencialmente grande al tamaño de la tabla hash. El valor de a suele ser un número primo lo suficientemente grande como para contener la cantidad de caracteres diferentes en el conjunto de caracteres de las claves potenciales. El hash de conversión de base de cadenas minimiza la cantidad de colisiones. [ 16 ] Los tamaños de datos disponibles pueden restringir la longitud máxima de la cadena que se puede hashear con este método. Por ejemplo, una palabra de 128 bits solo procesará una cadena alfabética de 26 caracteres (sin tener en cuenta mayúsculas ni minúsculas) con una base de 29; una cadena ASCII imprimible se limita a 9 caracteres con una base de 97 y una palabra de 64 bits. Sin embargo, las claves alfabéticas suelen ser de longitud moderada, ya que deben almacenarse en la tabla hash. Las cadenas de caracteres numéricos no suelen presentar problemas; 64 bits pueden contar hasta 10¹⁹ , o 19 dígitos decimales con base 10.

Hash rodante

En algunas aplicaciones, como la búsqueda de subcadenas , se puede calcular una función hash h para cada subcadena de k caracteres de una cadena dada de n caracteres, avanzando una ventana de ancho k caracteres a lo largo de la cadena, donde k es un entero fijo y n > k . La solución directa, que consiste en extraer dicha subcadena en cada posición de carácter del texto y calcular h por separado, requiere un número de operaciones proporcional a k · n . Sin embargo, con la elección adecuada de h , se puede utilizar la técnica de hash rodante para calcular todos esos hashes con un esfuerzo proporcional a mk + n, donde m es el número de ocurrencias de la subcadena. [ 17 ]  

El algoritmo más conocido de este tipo es Rabin-Karp, con un rendimiento en el mejor y promedio caso de O ( n + mk ) y en el peor caso de O ( n · k ) (para ser justos, el peor caso aquí es gravemente patológico: tanto la cadena de texto como la subcadena están compuestas por un solo carácter repetido, como t ="AAAAAAAAAAA", y s ="AAA"). La función hash utilizada para el algoritmo suele ser la huella digital de Rabin , diseñada para evitar colisiones en cadenas de caracteres de 8 bits, pero también se utilizan otras funciones hash adecuadas.

Hash difuso

El hashing difuso , también conocido como hashing de similitud, [ 18 ] es una técnica para detectar datos que son similares , pero no exactamente iguales, a otros datos. Esto contrasta con las funciones hash criptográficas , que están diseñadas para tener hashes significativamente diferentes incluso para diferencias mínimas. El hashing difuso se ha utilizado para identificar malware [ 19 ] [ 20 ] y tiene potencial para otras aplicaciones, como la prevención de pérdida de datos y la detección de múltiples versiones de código. [ 21 ] [ 22 ]

Hash perceptivo

El hash perceptual es el uso de un algoritmo de huella digital que produce un fragmento, hash o huella digital de varias formas de multimedia . [ 23 ] [ 24 ] Un hash perceptual es un tipo de hash sensible a la localidad , que es análogo si las características de la multimedia son similares. Esto contrasta con el hash criptográfico , que se basa en el efecto de avalancha de un pequeño cambio en el valor de entrada que crea un cambio drástico en el valor de salida. Las funciones de hash perceptual se utilizan ampliamente en la detección de casos de infracción de derechos de autor en línea , así como en la informática forense debido a la capacidad de tener una correlación entre hashes para que se puedan encontrar datos similares (por ejemplo, con una marca de agua diferente ).

Análisis

Los peores resultados para una función hash pueden evaluarse de dos maneras: teórica y práctica. El peor caso teórico es la probabilidad de que todas las claves se asignen a una sola ranura. El peor caso práctico es la secuencia de sondeo más larga esperada (función hash + método de resolución de colisiones). Este análisis considera el hashing uniforme, es decir, que cualquier clave se asignará a cualquier ranura con una probabilidad de 1/ m , característica de las funciones hash universales.

Mientras que Knuth se preocupa por los ataques adversarios a los sistemas en tiempo real, [ 25 ] Gonnet ha demostrado que la probabilidad de tal caso es "ridículamente pequeña". Su representación fue que la probabilidad de que k de n claves se asignen a una sola ranura es α k / ( e α k !) , donde α es el factor de carga, n / m . [ 26 ]

Historia

El término hash ofrece una analogía natural con su significado no técnico (trocear o desordenar algo), dado que las funciones hash revuelven sus datos de entrada para obtener su salida. [ 27 ] : 514 En su investigación sobre el origen preciso del término, Donald Knuth señala que, si bien Hans Peter Luhn de IBM parece haber sido el primero en utilizar el concepto de función hash en un memorando fechado en enero de 1953, el término en sí no apareció en la literatura publicada hasta finales de la década de 1960, en los Principios de los sistemas informáticos digitales de Herbert Hellerman , aunque para entonces ya era jerga extendida. [ 27 ] : 547–548

Véase también

Notas

  1. Esto resulta útil en casos donde las claves son ideadas por un agente malicioso, por ejemplo, en el contexto de un ataque DoS.
  2. El ASCII simplees una codificación de caracteres de 7 bits, aunque a menudo se almacena en bytes de 8 bits con el bit de orden superior siempre a cero. Por lo tanto, para el ASCII simple, los bytes solo tienen 2⁷ = 128 valores válidos, y la tabla de traducción de caracteres solo tiene esta cantidad de entradas.
  3. Por ejemplo, para n=15, k=4, t=6,S={1,2,3,4,5,6,8,10,12,9}{\displaystyle S=\{1,2,3,4,5,6,8,10,12,9\}}[Knuth]
  4. Knuth convenientemente deja la demostración de esto al lector.
  5. Grandes sistemas de Unisys.

Referencias

  1. Aggarwal, Kirti; Verma, Harsh K. (19 de marzo de 2015). Hash_RC6 — Algoritmo de hash de longitud variable que utiliza RC6 . Conferencia Internacional de 2015 sobre Avances en Ingeniería Informática y Aplicaciones (ICACEA). doi : 10.1109/ICACEA.2015.7164747 .
    • "resumen hash" . Centro de recursos de seguridad informática - Glosario . NIST .
    • "resumen del mensaje" . Centro de recursos de seguridad informática - Glosario . NIST .
  2. 1 2 3 4 Knuth, Donald E. (1973). El arte de la programación informática, vol. 3, Ordenación y búsqueda . Reading, MA, Estados Unidos: Addison-Wesley . Bibcode : 1973acp..book.....K . ISBN 978-0-201-03803-3.
  3. Stokes, Jon (2002-07-08). "Understanding CPU caching and performance" . Ars Technica . Recuperado el 2022-02-06 .
  4. Menezes, Alfred J.; van Oorschot, Paul C.; Vanstone, Scott A (1996). Manual de criptografía aplicada . CRC Press. ISBN 978-0849385230.
  5. "Acerca de la explicación de las pruebas y mediciones de funciones hash en Wikipedia" . Validado cruzadamente. 9 de julio de 2026.
  6. Castro, Julio César Hernández; et al. (3 de febrero de 2005). "La prueba de aleatoriedad del criterio de avalancha estricta". Matemáticas y computadoras en simulación . 68 (1). Elsevier : 1–7 . doi : 10.1016/j.matcom.2004.09.001 . S2CID 18086276 .  
  7. 1 2 Sharupke, Malte (16 de junio de 2018). "Fibonacci Hashing: La optimización que el mundo olvidó" . Probably Dance .
  8. Wagner, Urs; Lugrin, Thomas (2023), "Funciones hash", en Mulder, Valentin; Mermoud, Alain; Lenders, Vincent; Tellenbach, Bernhard (eds.), Tendencias en tecnologías de protección de datos y cifrado , Cham: Springer Nature Switzerland, pp. 21–24 , doi : 10.1007/978-3-031-33386-6_5 , ISBN  978-3-031-33386-6
  9. "3. Modelo de datos — Documentación de Python 3.6.1" . docs.python.org . Consultado el 24 de marzo de 2017 .
  10. 1 2 Sedgewick, Robert (2002). "14. Hashing". Algorithms in Java (3.ª ed.). Addison Wesley. ISBN  978-0201361209.
  11. Dolev, Shlomi; Lahiani, Limor; Haviv, Yinnon (2013). "Unique permutation hashing" . Theoretical Computer Science . 475 : 59–65 . doi : 10.1016/j.tcs.2012.12.047 .
  12. "CS 3110 Lección 21: Funciones hash" . Sección "Hashing multiplicativo".
  13. Zobrist, Albert L. (abril de 1970), Un nuevo método de hash con aplicación para juegos (PDF) , Informe técnico n.º 88, Madison, Wisconsin: Departamento de Ciencias de la Computación, Universidad de Wisconsin.
  14. Aho, A.; Sethi , R .; Ullman, JD (1986). Compiladores: Principios, técnicas y herramientas . Reading, MA: Addison-Wesley . pág. 435. ISBN  0-201-10088-6.
  15. Ramakrishna, MV; Zobel, Justin (1997). "Rendimiento práctico de las funciones hash de cadenas" . Sistemas de bases de datos para aplicaciones avanzadas '97 . DASFAA 1997. págs. 215–224 . CiteSeerX 10.1.1.18.7520 . doi : 10.1142/9789812819536_0023 . ISBN   981-02-3107-5. S2CID 8250194 . Consultado el 06-12-2021 . 
  16. Singh, NB Un manual de algoritmos . NB Singh.
  17. Breitinger, Frank (mayo de 2014). "Publicación especial NIST 800-168" (PDF) . Publicaciones NIST . doi : 10.6028/NIST.SP.800-168 . Consultado el 11 de enero de 2023 .
  18. Pagani, Fabio; Dell'Amico, Matteo; Balzarotti, Davide (13 de marzo de 2018). «Más allá de la precisión y la exhaustividad» (PDF) . Actas de la Octava Conferencia ACM sobre Seguridad y Privacidad de Datos y Aplicaciones . Nueva York, NY, EE. UU.: ACM. págs. 354–365 . doi : 10.1145/3176258.3176306 . ISBN  9781450356329. Consultado el 12 de diciembre de 2022 .
  19. Sarantinos, Nikolaos; Benzaïd, Chafika; Arabiat, Omar (2016). "Análisis forense de malware: el valor de los algoritmos de hash difuso para identificar similitudes" . 2016 IEEE Trustcom/BigDataSE/ISPA (PDF) . págs. 1782–1787 . doi : 10.1109/TrustCom.2016.0274 . ISBN  978-1-5090-3205-1. S2CID 32568938 . 10.1109/TrustCom.2016.0274. 
  20. Kornblum, Jesse (2006). "Identificación de archivos casi idénticos mediante hash por partes activado por contexto" . Investigación Digital . 3, Suplemento (septiembre de 2006): 91–97 . doi : 10.1016/j.diin.2006.06.015 .
  21. Oliver, Jonathan; Cheng, Chun; Chen, Yanggui (2013). "TLSH: un hash sensible a la localidad" (PDF) . Cuarto Taller sobre Ciberdelincuencia y Computación Confiable de 2013. IEEE. págs. 7–13 . doi : 10.1109/ctc.2013.9 . ISBN  978-1-4799-3076-0. Consultado el 12 de diciembre de 2022 .
  22. Buldas, Ahto; Kroonmaa, Andrés; Laanoja, Risto (2013). "Infraestructura de firmas sin llave: cómo construir árboles hash distribuidos globalmente". En Riis, Nielson H.; Gollmann, D. (eds.). Sistemas TI seguros. NordSec 2013 . Apuntes de conferencias sobre informática. vol. 8208. Berlín, Heidelberg: Springer. doi : 10.1007/978-3-642-41488-6_21 . ISBN  978-3-642-41487-9La infraestructura de firmas sin clave (KSI) es un sistema distribuido globalmente que proporciona servicios de firma digital con soporte de servidor y sellado de tiempo. Se crean árboles hash globales por segundo y se publican sus valores hash raíz. Analizamos algunos problemas de calidad del servicio que surgen en la implementación práctica y presentamos soluciones para evitar puntos únicos de fallo y garantizar un servicio con una latencia razonable y estable. Guardtime AS lleva cinco años operando una infraestructura KSI. Resumimos cómo se construyó la infraestructura KSI y las lecciones aprendidas durante el período de operación del servicio.
  23. Klinger, Evan; Starkweather, David. "pHash.org: Página principal de pHash, la biblioteca de hash perceptual de código abierto" . pHash.org . Consultado el 5 de julio de 2018. pHash es una biblioteca de software de código abierto publicada bajo la licencia GPLv3 que implementa varios algoritmos de hash perceptual y proporciona una API similar a C para usar esas funciones en sus propios programas. pHash está escrito en C++.
  24. Knuth, Donald E. (1975). El arte de la programación informática, vol. 3, Ordenación y búsqueda . Reading, MA: Addison-Wesley . pág. 540. 
  25. Gonnet, G. (1978). Longitud esperada de la secuencia de sonda más larga en la búsqueda de códigos hash (Informe técnico). Ontario, Canadá: Universidad de Waterloo . CS-RR-78-46.
  26. 1 2 Knuth, Donald E. (2000). El arte de la programación informática, vol. 3, Ordenación y búsqueda (2.ª ed., 6.ª reimpresión, edición actualizada y revisada ). Boston [ua]: Addison-Wesley. ISBN  978-0-201-89685-5.
  • La función hash de Goulburn ( PDF ) por Mayur Patel
  • Construcción de funciones hash para la recuperación de datos textuales y geométricos ( PDF ) Últimas tendencias en informática, vol. 2, págs.  483-489, Conferencia CSCC, Corfú, 2010