O que é uma boa função Hash?

130

O que é uma boa função Hash? Vi muitas funções e aplicativos de hash em meus cursos de estruturas de dados na faculdade, mas percebi que é muito difícil criar uma boa função de hash. Como regra geral, para evitar colisões, meu professor disse que:

function Hash(key)
  return key mod PrimeNumber
end

(mod é o operador% em C e idiomas semelhantes)

com o número primo para ser o tamanho da tabela de hash. Entendo que é uma função um tanto boa para evitar colisões e rápida, mas como posso fazer uma melhor? Existem funções de hash melhores para chaves de string e teclas numéricas?

Hoffmann
fonte
34
Você já pensou em usar uma ou mais das seguintes funções hash uso geral: partow.net/programming/hashfunctions/index.html
No fnv_func, o tipo de p [i] é char, o que acontecerá com h após a primeira iteração? Foi feito de propósito?
5
@martinatime disse: Há um monte de informações sobre funções de hash na wikipedia en.wikipedia.org/wiki/Hash_function e a parte inferior deste artigo partow.net/programming/hashfunctions/index.html possui algoritmos implementados em vários idiomas.
2501 28/06

Respostas:

33

Para fazer pesquisas "normais" da tabela de hash em basicamente qualquer tipo de dados - este de Paul Hsieh é o melhor que eu já usei.

http://www.azillionmonkeys.com/qed/hash.html

Se você se preocupa com criptograficamente seguro ou qualquer outra coisa mais avançada, o YMMV. Se você deseja apenas uma função hash de uso geral incrível para uma pesquisa de tabela de hash, é isso que você está procurando.

Chris Harris
fonte
Obrigado pelo link informativo! Conheço algumas análises de Bob Jenkins e outras que apontam para boas funções de hash universalmente aceitáveis, mas ainda não encontrei essa.
9289 Konrad Rudolph
Eu tinha lido a partir do site de Jenkins que SFH é um dos melhores em seguida, mas acho que Murmur pode fazer melhor, ver este excelente resposta: programmers.stackexchange.com/questions/49550/...
Nawfal
2
YMMV de quê?
cobarzan
3
@cobarzan Sua milhagem pode variar
ProgrammerDan
2
A função hash de Hsieh é horrível, com uma ordem de magnitude mais colisões do que queremos. Em particular, cadeias que diferem apenas nos últimos 4 bytes podem colidir facilmente. Se você tiver uma cadeia de 30 caracteres, que diferem nos últimos 4 bytes, após 28 bytes terem sido processados, os hashes diferem apenas nos últimos 2 bytes. Isso significa que você está GARANTIDO com uma colisão para um dos valores de dois bytes restantes. (Sim, é rápido Então, o que..)
Andrew Lazarus
51

Não existe uma “boa função hash” para hashes universais (ed. Sim, eu sei que existe algo como “hash universal”, mas não foi isso que eu quis dizer). Dependendo do contexto, diferentes critérios determinam a qualidade de um hash. Duas pessoas já mencionaram SHA. Este é um hash criptográfico e não é bom para tabelas de hash, o que você provavelmente quer dizer.

As tabelas de hash têm requisitos muito diferentes. Ainda assim, é difícil encontrar uma boa função de hash universalmente, porque tipos de dados diferentes expõem informações diferentes que podem ser hash. Como regra geral, é bom considerar todas as informações que um tipo mantém igualmente. Isso nem sempre é fácil ou até possível. Por razões de estatística (e, portanto, colisão), também é importante gerar uma boa dispersão no espaço do problema, ou seja, todos os objetos possíveis. Isso significa que, ao fazer o hash de números entre 100 e 1050, não é bom deixar o dígito mais significativo desempenhar um papel importante no hash porque, para ~ 90% dos objetos, esse dígito será 0. É muito mais importante deixar os três últimos dígitos determinam o hash.

Da mesma forma, ao fazer hash de strings, é importante considerar todos os caracteres - exceto quando se sabe de antemão que os três primeiros caracteres de todas as strings serão os mesmos; considerando estes, então, é um desperdício.

Este é realmente um dos casos em que aconselho a ler o que Knuth tem a dizer em The Art of Computer Programming , vol. 3. Outra boa leitura é The Art of Hashing, de Julienne Walker .

Konrad Rudolph
fonte
1
Konrad, você certamente está correto do ponto de vista teórico, mas você já tentou usar a função de hash Paul Hsieh que mencionei no meu comentário? É realmente muito bom contra muitos tipos diferentes de dados!
311 Chris Chris
9

Existem dois objetivos principais das funções de hash:

  • dispersar pontos de dados uniformemente em n bits.
  • para identificar com segurança os dados de entrada.

É impossível recomendar um hash sem saber para que você o está usando.

Se você está apenas criando uma tabela de hash em um programa, não precisa se preocupar com o quão reversível ou hackável é o algoritmo ... SHA-1 ou AES é completamente desnecessário para isso, é melhor usar uma variação do FNV . O FNV alcança melhor dispersão (e, portanto, menos colisões) do que um mod simples, como você mencionou, e é mais adaptável a diferentes tamanhos de entrada.

Se você estiver usando os hashes para ocultar e autenticar informações públicas (como hash de uma senha ou de um documento), use um dos principais algoritmos de hash verificados pelo escrutínio público. O Hash Function Lounge é um bom lugar para começar.

Myrddin Emrys
fonte
link atualizado para o Hash Function Lounge: larc.usp.br/~pbarreto/hflounge.html
Tim Partridge
Quão bem o FNV suporta colisão de aniversário em comparação com, digamos, o mesmo número de bits de um SHA1?
Kevin Hsu
@ Kevin Desde que as características avalanche de um hash sejam boas (pequenas alterações na entrada = grandes alterações na saída), as colisões de aniversários são simplesmente uma função dos bits no hash. O FNV-1a é excelente a esse respeito, e você pode ter tantos ou poucos bits no hash quanto desejar (embora seja necessário um pouco de esforço extra para obter uma contagem de bits que não é uma potência de 2).
Myrddin Emrys
5

Este é um exemplo de uma boa e também um exemplo de por que você nunca iria querer escrever uma. É um Hash Fowler / Noll / Vo (FNV) que é partes iguais de gênio da ciência da computação e puro vodu:

unsigned fnv_hash_1a_32 ( void *key, int len ) {
    unsigned char *p = key;
    unsigned h = 0x811c9dc5;
    int i;

    for ( i = 0; i < len; i++ )
      h = ( h ^ p[i] ) * 0x01000193;

   return h;
}

unsigned long long fnv_hash_1a_64 ( void *key, int len ) {
    unsigned char *p = key;
    unsigned long long h = 0xcbf29ce484222325ULL;
    int i;

    for ( i = 0; i < len; i++ )
      h = ( h ^ p[i] ) * 0x100000001b3ULL;

   return h;
}

Editar:

  • Landon Curt Noll recomenda em seu site o algoritmo FVN-1A sobre o algoritmo original FVN-1: O algoritmo aprimorado dispersa melhor o último byte no hash. Eu ajustei o algoritmo de acordo.
Nick Van Brunt
fonte
3
Você pode consultar este site para obter algumas informações sobre por que esses valores são escolhidos: isthe.com/chongo/tech/comp/fnv/#fnv-prime
Cthutu
Saúde. Essa função hash de 64 bits curta, simples, eficiente, genérica e eficaz era exatamente o que eu precisava.
mattarod
3

Eu diria que a principal regra geral é não rolar a sua. Tente usar algo que foi completamente testado, por exemplo, SHA-1 ou algo nesse sentido.

Einar
fonte
Ele não parece precisar de nada criptograficamente seguro para que o SHA-1 seja um exagero.
Erik
a propósito, embora nenhuma colisão para o SHA-1 tenha sido encontrada, acredita-se que seja uma questão de anos ou meses antes que uma seja encontrada. Eu recomendaria usar o SHA-256.
Samuel Allan
1

Uma boa função hash possui as seguintes propriedades:

  1. Dado um hash de uma mensagem, é inviável computacionalmente para um invasor encontrar outra mensagem de modo que seus hashes sejam idênticos.

  2. Dado um par de mensagens, m 'e m, é computacionalmente inviável encontrar dois tais que h (m) = h (m')

Os dois casos não são os mesmos. No primeiro caso, existe um hash pré-existente para o qual você está tentando encontrar uma colisão. No segundo caso, você está tentando encontrar quaisquer duas mensagens que se chocam. A segunda tarefa é significativamente mais fácil devido ao "paradoxo" do aniversário.

Onde o desempenho não é um problema tão grande, você sempre deve usar uma função hash segura. Existem ataques muito inteligentes que podem ser executados forçando colisões em um hash. Se você usar algo forte desde o início, se protegerá disso.

Não use MD5 ou SHA-1 em novos modelos. A maioria dos criptógrafos, inclusive eu, os consideraria quebrados. A principal fonte de fraqueza em ambos os projetos é que a segunda propriedade, que descrevi acima, não se aplica a essas construções. Se um invasor pode gerar duas mensagens, m e m ', que ambos hash com o mesmo valor, eles podem usar essas mensagens contra você. O SHA-1 e o MD5 também sofrem ataques de extensão de mensagem, que podem enfraquecer fatalmente seu aplicativo se você não tomar cuidado.

Um hash mais moderno, como o Whirpool, é uma escolha melhor. Ele não sofre com esses ataques de extensão de mensagem e usa a mesma matemática que o AES usa para provar a segurança contra uma variedade de ataques.

Espero que ajude!

Simon Johnson
fonte
1
Eu acho que a recomendação da função de hash criptográfico é um péssimo conselho nesse caso.
Slava
@Slava: Por quê? Quais são as suas razões para dizer que "uma função hash criptográfica é um péssimo conselho neste caso?" Por que esse conselho é ruim? Quais são as desvantagens relativas que o tornam?
Deixe-me mexer nisso
2
@Mowzer porque uma função de hash usada no mapa de hash deve ser rápida e leve (supondo que ainda forneça um bom hash), os hashes de criptografia explicitamente foram criados para serem computacionalmente caros para evitar ataques de força bruta.
Slava
1

O que você está dizendo aqui é que deseja que um que use tenha resistência à colisão. Tente usar o SHA-2. Ou tente usar uma (boa) cifra de bloco em uma função de compressão unidirecional (nunca tentei isso antes), como o AES no modo Miyaguchi-Preenel. O problema é que você precisa:

1) ter um IV. Tente usar os primeiros 256 bits das partes fracionárias da constante de Khinchin ou algo assim. 2) tem um esquema de preenchimento. Fácil. Carrinho de mão de um hash como MD5 ou SHA-3 (Keccak [pronuncia-se 'ket-chak']). Se você não se importa com a segurança (alguns outros disseram isso), veja o FNV ou o lookup2 de Bob Jenkins (na verdade, eu sou o primeiro a recomendar lookup2). Tente também o MurmurHash, é rápido (verifique: 0,16 cpb )

Gavriel Feria
fonte