Qual é a função de hashing. Funções criptográficas de hash

Os algoritmos de pesquisa que consideramos são geralmente baseados em uma operação de comparação abstrata. Desta série, destaca-se essencialmente o método de distribuição da pesquisa, descrito em "Tabelas de símbolos e árvores binárias de pesquisa", em que o elemento com a chave i é armazenado na i-ésima posição da tabela, o que permite consulte-o diretamente. A pesquisa distribuída usa os valores das chaves como índices da matriz, em vez dos operandos da operação de comparação; o método em si depende do fato de que as chaves são inteiros distintos do mesmo intervalo que os índices da tabela. Neste capítulo, veremos o hashing, uma pesquisa de distribuição avançada usada em aplicativos de pesquisa mais típicos em que as chaves não têm essas propriedades convenientes. O resultado final da aplicação esta abordagem nem um pouco como métodos baseados em comparação - em vez de percorrer as estruturas de dados do dicionário comparando as chaves de pesquisa com as chaves nos elementos, tentamos acessar os elementos na tabela diretamente realizando a conversão aritmética das chaves em endereços de tabela .

Os algoritmos de pesquisa que usam hashing consistem em dois partes separadas... A primeira etapa é calcular uma função hash que converte a chave de pesquisa em um endereço na tabela. Idealmente, chaves diferentes seriam mapeadas para endereços diferentes, mas geralmente duas ou mais chaves diferentes podem fornecer o mesmo endereço na tabela. Portanto, a segunda parte da pesquisa de hashing é o processo de resolução de colisão, que lida com essas chaves. Uma das técnicas de resolução de conflitos que veremos neste capítulo usa listas vinculadas, portanto, encontra uso direto em situações dinâmicas em que é difícil prever o número de chaves de pesquisa com antecedência. Os outros dois métodos de resolução de colisão alcançam alta atuação pesquisa à medida que os elementos são armazenados em uma matriz fixa. Veremos como esses métodos podem ser aprimorados para que possam ser usados ​​mesmo nos casos em que o tamanho da tabela não pode ser previsto com antecedência.

Hashing - bom exemplo equilíbrio entre o tempo e a memória. Se não houvesse limites para a quantidade de memória utilizada, qualquer busca poderia ser realizada com apenas um acesso à memória, simplesmente utilizando a chave como endereço de memória, como em uma busca por alocação. No entanto, esse caso ideal geralmente é inatingível, pois as chaves longas podem exigir uma grande quantidade de memória. Por outro lado, se não houvesse restrições sobre tempo de espera, pode-se sobreviver com uma quantidade mínima de memória usando o método de busca sequencial. O hash é uma maneira de usar uma quantidade aceitável de memória e tempo e encontrar um equilíbrio entre esses dois extremos. Em particular, qualquer equilíbrio pode ser mantido simplesmente mudando o tamanho da tabela, em vez de reescrever o código ou escolher outros algoritmos.

O hash é um dos problemas clássicos da ciência da computação: seus vários algoritmos foram estudados detalhadamente e são amplamente utilizados. Veremos que, com suposições vagas, podemos esperar dar suporte a operações de localização e inserção em tabelas de símbolos de tempo constante, independentemente do tamanho da tabela.

Esse valor esperado é o desempenho teórico ideal para qualquer implementação de tabela de símbolos, mas o hashing ainda não é uma panacéia por dois motivos principais. Inicialmente, tempo de espera depende do comprimento da chave, que pode ser significativo em aplicativos reais que usam chaves longas. Em segundo lugar, o hashing não fornece implementações eficientes de outras operações da tabela de símbolos, como selecionar ou classificar. Neste capítulo, examinaremos mais de perto essas e outras questões.

Funções de hash

Em primeiro lugar, é necessário resolver o problema de cálculo de uma função hash que converte chaves em endereços de tabelas. Normalmente, a implementação deste cálculo aritmético não é difícil, mas você ainda precisa ter cuidado para não se deparar com vários rochas subaquáticas... Se você tem uma tabela que pode conter M elementos, você precisa de uma função que converta as chaves em inteiros no intervalo. Uma função hash ideal deve ser fácil de calcular e assemelhar-se a uma função aleatória: para quaisquer argumentos, os resultados devem, em certo sentido, ser igualmente prováveis.

A função hash depende do tipo da chave. Estritamente falando, uma função hash separada é necessária para cada tipo de chave possível. Para melhorar a eficiência, geralmente é desejável evitar conversões de tipo explícitas e, em vez disso, ir para a ideia de olhar para a representação binária de chaves em palavra de máquina como um número inteiro que pode ser usado em cálculos aritméticos. O hash veio antes das línguas alto nível- nos primeiros computadores, era comum tratar um valor como uma chave de string ou um inteiro. Em algumas linguagens de alto nível, é difícil criar programas que dependem da representação de chaves em um determinado computador, uma vez que esses programas são inerentemente dependentes da máquina e, portanto, difíceis de transferir para outro computador. As funções de hash geralmente dependem do processo de conversão de chaves em inteiros, portanto, pode ser difícil obter independência de máquina e eficiência em implementações de hash. Normalmente, chaves inteiras simples ou de ponto flutuante podem ser convertidas com apenas uma operação de máquina, mas chaves de string e outros tipos de chaves compostas são mais caras e mais atenção à eficiência.

Provavelmente, a situação mais simples é quando as teclas são números de ponto flutuante de um intervalo fixo. Por exemplo, se as chaves são números maiores que 0 e menores que 1, você pode simplesmente multiplicá-las por M, arredondar o resultado para o número inteiro inferior e obter um endereço no intervalo entre 0 e M - 1; tal exemplo é mostrado na Fig. 14,1. Se as chaves forem maiores do que se menores do que t, elas podem ser escaladas subtraindo s e dividindo por ts, trazendo-as para o intervalo de valores entre 0 e 1 e, em seguida, multiplicando por M para obter o endereço na tabela .


Arroz. 14,1.

Para converter números de ponto flutuante no intervalo entre 0 e 1 em índices de uma tabela cujo tamanho é 97, esses números são multiplicados por 97. Neste exemplo, ocorreram três colisões: para os índices 17, 53 e 76. Valores hash são determinados pelos bits mais altos da chave, os bits menos significativos não desempenham nenhum papel. Um dos objetivos do desenvolvimento de uma função hash é corrigir esse desequilíbrio para que cada bit seja levado em consideração durante o cálculo.

Se as chaves forem inteiros de w bits, elas podem ser convertidas em números de ponto flutuante e divididos por 2 w para obter números de ponto flutuante entre 0 e 1, e então multiplicados por M como no parágrafo anterior. Se as operações de ponto flutuante demoram muito e os números não são grandes o suficiente para causar estouro, o mesmo resultado pode ser obtido usando operações aritméticas inteiras: você precisa multiplicar a chave por M e, em seguida, realizar um deslocamento para a direita por w dígitos para divida por 2 w (ou, se a multiplicação estourar, faça o turno e depois a multiplicação). Esses métodos são inúteis para o hash, a menos que as chaves sejam distribuídas uniformemente no intervalo, uma vez que o valor do hash é determinado apenas pelos dígitos iniciais da chave.

Mais simples e método eficaz para inteiros de w bits - um dos, talvez, os métodos de hash mais usados ​​- escolhendo uma tabela de números primos como o tamanho M e calculando o restante de k por M, ou seja, h (k) = k mod M para qualquer chave inteira k. Essa função é chamada de função hash modular. É muito fácil de calcular (k% M em C ++) e é eficiente para obter uma distribuição uniforme de valores-chave entre valores menores que M. Um pequeno exemplo é mostrado na Fig. 14,2.


Arroz. 14,2.

As três colunas à direita mostram o resultado do hash das chaves de 16 bits listadas à esquerda usando as seguintes funções:

v% 97 (esquerda)

v% 100 (centro) e

(int) (a * v)% 100 (direita),

onde a = 0,618033. Os tamanhos das tabelas para essas funções são 97, 100 e 100, respectivamente. Os valores parecem aleatórios (já que as chaves são aleatórias). A segunda função (v% 100) usa apenas os dois dígitos mais à direita das teclas e, portanto, pode mostrar um desempenho ruim para chaves não aleatórias.

O hash modular também se aplica a chaves de ponto flutuante. Se as chaves estiverem em um intervalo pequeno, você pode escalá-las para números entre 0 e 1,2 w para obter inteiros de w bits e, em seguida, usar uma função hash modular. Outra opção é simplesmente usar a representação binária da chave como o operando da função hash modular (se disponível).

O hashing modular é utilizado em todos os casos em que há acesso aos bits que compõem as chaves, sejam eles inteiros representados por uma palavra de máquina, uma sequência de caracteres compactados em uma palavra de máquina ou qualquer outra. opção possível... Uma sequência de caracteres aleatórios compactados em uma palavra de máquina não é exatamente igual a chaves inteiras aleatórias, uma vez que nem todos os bits são usados ​​para codificação. Mas ambos os tipos (e qualquer outro tipo de chave codificada para caber em uma palavra de máquina) podem ser transformados em índices aleatórios em uma pequena mesa.

A principal razão para escolher uma tabela de hash principal como o tamanho M para hashing modular é mostrada na Fig. 14,3. Este exemplo de dados de caracteres de 7 bits trata a chave como um número de base 128 - um dígito para cada caractere da chave. A palavra agora corresponde ao número 1816567, que também pode ser escrito como

porque em ASCII os caracteres n, o e w correspondem aos números 1568 = 110, 1578 = 111 e 1678 = 119. A escolha do tamanho da tabela M = 64 para este tipo de chave é malsucedida, porque adicionar valores que são múltiplos de 64 (ou 128) ax não altera o valor de x mod 64 - para qualquer chave, o valor de a função hash é o valor dos últimos 6 bits dessa chave. Obviamente, uma boa função hash deve considerar todos os dígitos da chave, especialmente para chaves simbólicas. Situações semelhantes podem surgir quando M contém um fator de potência de 2. A maneira mais simples para evitar isso, escolha um número primo como M.


Arroz. 14,3.

Cada linha desta tabela contém: uma palavra de 3 letras, a representação ASCII dessa palavra como um número de 21 bits em notação octal e decimal e funções hash modulares padrão para tabelas de tamanhos 64 e 31 (as duas colunas mais à direita) . O tamanho da tabela 64 leva a resultados indesejáveis ​​porque apenas os bits mais à direita da chave são usados ​​para obter o valor de hash, e as letras nas palavras da linguagem comum são distribuídas de forma desigual. Por exemplo, todas as palavras que terminam com a letra y têm um valor hash de 57. Em contraste, um valor simples de 31 causa menos colisões em uma tabela com mais da metade do tamanho.

O hashing modular é muito fácil de implementar, exceto que o tamanho da tabela deve ser um número primo. Para alguns aplicativos, você pode se contentar com um pequeno número primo conhecido ou procurar em uma lista de números primos conhecidos por um que esteja próximo do tamanho de tabela necessário. Por exemplo, números iguais a 2 t - 1 são primos quando t = 2, 3, 5, 7, 13, 17, 19 e 31(e para nenhum outro valor de t< 31 ): это известные простые числа Мерсенна. Чтобы динамически распределить таблицу нужного размера, нужно вычислить простое число, близкое к этому значению. Такое вычисление нетривиально (хотя для этого и существует остроумный алгоритм, который будет рассмотрен в части 5), поэтому на практике обычно используют таблицу заранее вычисленных значений (см. рис. 14.4). Использование модульного хеширования - не A única razão, pelo qual o tamanho da mesa deve ser transformado em um número primo; outro motivo é discutido na seção 14.4.


Arroz. 14,4.

Esta tabela dos maiores números primos menos de 2 n para , pode ser usado para alocar dinamicamente uma tabela hash quando você quiser que o tamanho da tabela seja um número primo. Para qualquer valor positivo dado no intervalo coberto, esta tabela pode ser usada para determinar um número primo que seja menos de 2 vezes diferente dele.

Outra maneira de lidar com chaves inteiras é combinar os métodos multiplicativos e modulares: você precisa multiplicar a chave por uma constante entre 0 e 1 e, em seguida, fazer a divisão módulo M. Em outras palavras, você precisa usar uma função. Há uma relação entre os valores M e a raiz efetiva da chave que poderia teoricamente levar a um comportamento anômalo, mas se você usar um valor arbitrário de a, em aplicação real dificilmente há qualquer problema. Freqüentemente, o valor φ = 0,618033 ... (proporção áurea) é escolhido como a.

Muitas outras variações foram exploradas neste tópico, em particular funções hash que podem ser implementadas com instruções de máquina eficientes, como deslocamento e destaque mascarado (consulte a seção de links).

Em muitos aplicativos que usam tabelas de símbolos, as chaves não são números e não são necessariamente curtas; mais frequentemente, são cadeias alfanuméricas, que podem ser bastante longas. Então, como você calcula a função hash para uma palavra como averylongkey?

No código ASCII de 7 bits, esta palavra corresponde ao número de 84 bits \ begin (alinhar *) 97 \ cdot 128 ^ (11) & + 118 \ cdot 128 ^ (10) + 101 \ cdot 128 ^ (9) + 114 \ cdot 128 ^ (8) + 121 \ cdot 128 ^ (7) \\ & + 108 \ cdot 128 ^ (6) + 111 \ cdot 128 ^ (5) + 110 \ cdot 128 ^ (4) + 103 \ cdot 128 ^ (3) \\ & + 107 \ cdot 128 ^ (2) + 101 \ cdot 128 ^ (1) + 121 \ cdot 128 ^ (0), \ end (alinhar *),

que é muito grande para realizar funções aritméticas normais na maioria dos computadores. E muitas vezes você precisa lidar com chaves muito mais longas.

Para calcular a função hash modular para chaves longas, elas são transformadas pedaço por pedaço. Você pode usar as propriedades aritméticas da função do módulo e usar o algoritmo de Horner (consulte a seção 4.9 "Tipos de dados abstratos"). Este método é baseado em outra forma de escrever os números correspondentes às chaves. Para este exemplo, escreva a seguinte expressão: \ begin (align *) (((((((((97 \ cdot 128 ^ (11)) & + 118) \ cdot 128 ^ (10) + 101) \ cdot 128 ^ (9) + 114) \ cdot 128 ^ (8) + 121) \ cdot 128 ^ (7) \\ & + 108) \ cdot 128 ^ (6) + 111) \ cdot 128 ^ (5) + 110) \ cdot 128 ^ (4) + 103) \ cdot 128 ^ (3) \\ & + 107) \ cdot 128 ^ (2) + 101) \ cdot 128 ^ (1) + 121. \ end (alinhar *)

Isso é número decimal, correspondendo à codificação de caracteres da string, pode ser calculada olhando-a da esquerda para a direita, multiplicando o valor acumulado por 128 e, em seguida, adicionando o valor do código do próximo caractere. No caso de uma string longa, essa forma de cálculo acabará resultando em um número maior do que o que um computador pode imaginar. No entanto, este número não é necessário, uma vez que apenas um (pequeno) resto é exigido de sua divisão por M. O resultado pode ser obtido sem mesmo armazenar um grande valor acumulado, uma vez que a qualquer momento do cálculo, você pode descartar um número que é um múltiplo de M - para cada multiplicação e adição, você só precisa armazenar o restante do módulo de divisão M. O resultado será o mesmo como se pudéssemos calcule um número longo e execute a divisão (ver. Exercício 14.10). Essa observação leva a uma maneira aritmética direta de calcular funções hash modulares para strings longas - consulte o Programa 14.1. Este programa usa um truque final: em vez da base 128, ele usa o número primo 127. A razão para essa mudança é discutida no próximo parágrafo.

Existem muitas maneiras de calcular funções hash com aproximadamente o mesmo custo que o hash modular usando o método de Horner (um ou dois operaçoes aritimeticas para cada personagem na chave). Para chaves aleatórias, esses métodos são praticamente os mesmos, mas as chaves reais raramente são aleatórias. A capacidade de randomizar chaves reais a um baixo custo leva à consideração de algoritmos de hash aleatórios, uma vez que precisamos de funções hash que geram índices aleatórios em uma tabela, independentemente da distribuição de chaves. A randomização é fácil, já que você não precisa literalmente aderir à definição de hashing modular - você só precisa usar todos os dígitos da chave para calcular um número inteiro menor que M.

M = 96 e a = 128 (topo),

M = 97 e a = 128 (centro) e

M = 96 e a = 127 (parte inferior)

A distribuição desigual no primeiro caso é resultado do uso desigual de letras e da persistência da desigualdade devido ao fato de que tanto o tamanho da mesa quanto o fator são múltiplos de 32. Os outros dois exemplos parecem aleatórios porque o tamanho da mesa e o fator são números relativamente primos.

O programa 14.1 mostra uma maneira de fazer isso: usando uma base simples em vez de uma potência de 2 e um inteiro correspondente à representação ASCII da string. Na fig. 14,5 fig. A Figura 14.5 mostra como essa mudança melhora a distribuição de chaves de string típicas. Em teoria, os valores de hash gerados pelo Programa 14.1 podem dar resultados ruins para tamanhos de tabela múltiplos de 127 (embora na prática isso provavelmente seja quase invisível); para criar um algoritmo aleatório, pode-se escolher o valor do multiplicador aleatoriamente. Uma abordagem ainda mais eficiente é usar valores aleatórios dos coeficientes no cálculo e diferentes valores aleatórios para cada dígito principal. Essa abordagem produz um algoritmo aleatório chamado hashing universal.

Em teoria, uma função hash universal ideal é aquela para a qual a probabilidade de uma colisão entre duas chaves diferentes em uma tabela de tamanho M é exatamente 1 / M. Pode ser provado que usar como coeficiente a no Programa 14.1, não um valor arbitrário fixo, mas uma sequência de valores diferentes aleatórios, transforma o hash modular em uma função hash universal. No entanto, o custo de gerar um novo número aleatório para cada caractere da chave geralmente é inaceitável. Na prática, a compensação mostrada no Programa 14.1 pode ser alcançada não armazenando uma matriz de diferentes números aleatórios para cada símbolo de chave, mas variando os coeficientes, gerando uma sequência pseudo-aleatória simples.

Para resumir, a fim de usar hashing para implementar uma tabela de símbolos abstratos, primeiro você precisa estender a interface do tipo abstrato para incluir uma operação hash, que mapeia chaves para inteiros não negativos menores que o tamanho da tabela M.

No âmbito deste artigo, direi a você o que é hash, porque é necessário, onde e como é usado, bem como os exemplos mais famosos.

Muitas tarefas de tecnologia da informação são altamente críticas para os dados. Por exemplo, se você precisar comparar dois arquivos de 1 KB cada e dois arquivos de 10 GB cada, este é um momento completamente diferente. Portanto, algoritmos que permitem operar com valores mais curtos e amplos são considerados muito populares.

Uma dessas tecnologias é o Hashing, que encontrou sua aplicação na solução de muitos problemas. Mas, eu acho que para você, como um usuário comum, ainda não está claro que tipo de animal é e para que serve. Portanto, além disso, tentarei explicar tudo nas palavras mais simples.

Observação: O material é destinado ao usuário comum e não contém muitos aspectos técnicos, porém é mais do que suficiente para um conhecimento básico.

O que é Hash ou Hashing?

Vou começar com os termos.

Função Hash, Função de Convoluçãoé um tipo especial de função que permite converter textos de comprimento arbitrário em um código de comprimento fixo (geralmente uma notação alfanumérica curta).

Hashingé o próprio processo de conversão dos textos-fonte.

Hash, código de hash, valor de hash, soma de hashé o valor de saída da função Hash, ou seja, o bloco resultante de comprimento fixo.

Como você pode ver, os termos têm uma descrição um tanto figurativa, a partir da qual é difícil entender para que serve tudo isso. Portanto, darei imediatamente um pequeno exemplo (falarei sobre o restante dos aplicativos um pouco mais tarde). Digamos que você tenha 2 arquivos de 10 GB. Como você pode descobrir rapidamente de qual você precisa? O nome do arquivo pode ser usado, mas é fácil renomeá-lo. Você pode ver as datas, mas depois de copiar os arquivos, as datas podem ser iguais ou em uma sequência diferente. O tamanho, como você mesmo entende, pouco pode ajudar (especialmente se os tamanhos são iguais ou você não olhou para os valores exatos dos bytes).

É aqui que esse Hash é necessário, que é um pequeno bloco formado a partir do texto-fonte do arquivo. Esses dois arquivos de 10 GB terão dois Hashcodes diferentes, mas curtos (algo como "ACCAC43535" e "BBB3232A42"). Usando-os, você pode descobrir rapidamente arquivo desejado, mesmo depois de copiar e alterar nomes.

Observação: Devido ao fato de Hash ser um conceito muito conhecido no mundo da informática e na Internet, muitas vezes tudo o que está relacionado a Hash é abreviado para esta palavra. Por exemplo, a frase "Eu uso hash MD5" na tradução significa que o site ou outro lugar usa o algoritmo de hash do padrão MD5.

Propriedades da função hash

Agora, falarei sobre as propriedades das funções Hash, para que seja mais fácil para você entender onde o Hash é usado e para que é necessário. Mas, primeiro, mais uma definição.

Colisão- esta é uma situação em que a mesma soma hash é obtida para dois textos diferentes. Como você mesmo entende, por ser um bloco de comprimento fixo, ele tem um número limitado de valores possíveis e, portanto, as repetições são possíveis.

E agora às próprias propriedades das funções Hash:

1. A entrada pode ser um texto de qualquer tamanho e a saída é um bloco de dados de comprimento fixo. Isso decorre da definição.

2. O hash sum dos mesmos textos deve ser o mesmo. Caso contrário, essas funções são simplesmente inúteis - é análogo a um número aleatório.

3. Boa função as convoluções devem ter uma boa distribuição. Concorde que se o tamanho do Hash de saída for, por exemplo, 16 bytes, então se a função retornar apenas 3 valores diferentes para qualquer texto, então não há sentido em tal função e esses 16 bytes (16 bytes são 2 ^ 128 opções, que é aproximadamente 3, 4 * 10 ^ 38 graus).

4. O quão bem a função reage às menores alterações no texto de origem. Um exemplo simples. Alterada 1 letra em um arquivo de 10 GB, o valor da função deve ser diferente. Se não for esse o caso, usar essa função é muito problemático.

5. A probabilidade de uma colisão. Um parâmetro muito complexo calculado sob certas condições. Mas, sua essência é que qual é o objetivo da função hash se a soma hash resultante muitas vezes coincide.

6. Velocidade de cálculo de Hash. Qual é a utilidade de uma função de convolução se leva muito tempo para ser computada? Nenhum, porque assim é mais fácil comparar os dados do arquivo ou usar uma abordagem diferente.

7. A complexidade de recuperar os dados originais do valor Hash. Essa característica é mais específica do que geral, pois nem sempre é necessária. No entanto, para os algoritmos mais conhecidos, essa característica é estimada. Por exemplo, você dificilmente pode obter o arquivo original desta função. No entanto, se houver um problema de colisão (por exemplo, você precisa encontrar qualquer texto que corresponda a esse Hash), essa característica pode ser importante. Por exemplo, senhas, mas mais sobre eles mais tarde.

8. Código-fonte aberto ou fechado de tal função. Se o código não for de código aberto, a complexidade da recuperação de dados, ou seja, a força criptográfica, permanece em questão. Em parte, isso é um problema de criptografia.

Agora podemos passar para a pergunta "para que serve tudo isso?"

Por que o Hash é necessário?

Existem apenas três objetivos principais das funções hash (ou melhor, seu propósito).

1. Verificar a integridade dos dados. V este caso tudo é simples, tal função deve ser calculada rapidamente e permitir com a mesma rapidez verificar se, por exemplo, um arquivo baixado da Internet não foi danificado durante a transmissão.

2. Aumento na velocidade de recuperação de dados. Um tamanho de bloco fixo permite que você obtenha muitas vantagens na solução de problemas de pesquisa. Nesse caso, a questão é que, tecnicamente, o uso de funções hash pode ter um efeito positivo no desempenho. Para tais funções, a probabilidade de colisões e uma boa distribuição são muito importantes.

3. Para necessidades criptográficas. Esta vista As funções de convolução são usadas nas áreas de segurança onde é importante que os resultados sejam difíceis de substituir ou onde é necessário tornar a tarefa de obtenção o mais difícil possível informação útil de Hash.

Onde e como o hash é usado?

Como você provavelmente já deve ter adivinhado, o Hash é usado em muitas tarefas. Aqui estão alguns deles:

1. As senhas geralmente não são armazenadas em texto não criptografado, mas na forma de hash-sum, o que permite um maior grau de segurança. Afinal, mesmo que um invasor obtenha acesso a esse banco de dados, ele ainda terá que gastar muito tempo para encontrar os textos apropriados para esses códigos hash. É aqui que a característica "a complexidade de recuperar os dados originais dos valores Hash" é importante.

Observação: Aconselho você a ler este artigo para obter algumas dicas para melhorar a segurança das senhas.

2. Na programação, incluindo bancos de dados. Claro, na maioria das vezes estamos falando sobre estruturas de dados que permitem pesquisa rápida... Aspecto puramente técnico.

3. Ao transferir dados em uma rede (incluindo a Internet). Muitos protocolos, como TCP / IP, incluem campos de verificação especiais que contêm o hash da mensagem original para que, se houver uma falha em algum lugar, isso não afete a transferência de dados.

4. Para vários algoritmos relacionados à segurança. Por exemplo, Hash é usado em assinaturas digitais eletrônicas.

5. Para verificar a integridade dos arquivos. Se você prestou atenção, muitas vezes na Internet você pode encontrar arquivos (por exemplo, arquivos) descrições adicionais com código hash. Essa medida é usada não apenas para que você não lance acidentalmente um arquivo danificado durante o download da Internet, mas também para que ocorram simplesmente falhas de hospedagem. Nesses casos, você pode verificar rapidamente o Hash e, se necessário, reenviar o arquivo.

6. Às vezes, funções hash são usadas para criar identificadores exclusivos (como parte de). Por exemplo, ao salvar fotos ou apenas arquivos, eles geralmente usam Hash nos nomes junto com a data e a hora. Isso evita sobrescrever arquivos com o mesmo nome.

Na verdade, quanto mais você avança, mais funções hash são usadas em tecnologia da Informação... Principalmente devido ao fato de que a quantidade de dados e a potência da maioria computadores simples cresceram muito. No primeiro caso, é mais sobre pesquisa e, no segundo, é mais sobre questões de segurança.

Funções de hash notáveis

As mais famosas são as três funções de hash a seguir.

Anotação: Nesta palestra, o conceito de uma função hash é formulado e também fornecido breve revisão algoritmos para a formação de funções hash. Além disso, a possibilidade de usar algoritmos de criptografia de bloco para gerar uma função hash é considerada.

O objetivo da palestra: familiarizar-se com o conceito de "função hash", bem como com os princípios de tais funções.

Conceito de função Hash

Função Hashé uma função matemática ou outra que, para uma string de comprimento arbitrário, calcula algum valor inteiro ou alguma outra string de comprimento fixo. Matematicamente, pode ser escrito assim:

onde M é a mensagem original, às vezes chamada protótipo e h é o resultado denominado valor hash (e também código hash ou mensagens de resumo(do inglês. resumo da mensagem)).

O significado da função hash é determinar o recurso característico da pré-imagem - o valor da função hash. Esse valor geralmente tem um determinado tamanho fixo, como 64 ou 128 bits. O código hash pode ser analisado posteriormente para resolver qualquer problema. Assim, por exemplo, o hashing pode ser usado para comparar dados: se dois arrays de dados têm códigos hash diferentes, os arrays são certamente diferentes; se eles forem iguais, os arrays provavelmente serão os mesmos. No caso geral, não há correspondência um a um entre os dados originais e o código hash devido ao fato de que o número de valores das funções hash é sempre menor que o número de variantes dos dados de entrada. Portanto, há muitas mensagens de entrada com os mesmos códigos hash (tais situações são chamadas colisões) A probabilidade de colisões desempenha um papel importante na avaliação da qualidade das funções hash.

As funções de hash são amplamente utilizadas na criptografia moderna.

A função hash mais simples pode ser construída usando a operação "sum modulo 2" da seguinte maneira: obtemos a string de entrada, adicionamos todos os bytes módulo 2 e retornamos o byte de resultado como o valor da função hash. O comprimento do valor hash, neste caso, será de 8 bits, independentemente do tamanho da mensagem de entrada.

Por exemplo, suponha que a mensagem digitalizada original fosse a seguinte (em formato hexadecimal):

Vamos traduzir a mensagem para a forma binária, escrever os bytes um sobre o outro e adicionar os bits em cada módulo de coluna 2:

0011 1110 0101 0100 1010 0000 0001 1111 1101 0100 ---------- 0110 0101

O resultado (0110 0101 (2) ou 65 (16)) será o valor hash.

No entanto, essa função hash não pode ser usada para fins criptográficos, por exemplo, para gerar Assinatura Eletrônica, uma vez que é muito fácil alterar o conteúdo da mensagem assinada sem alterar o valor da soma de verificação.

Portanto, a função hash considerada não é adequada para aplicações criptográficas. Na criptografia, uma função hash é considerada boa se for difícil criar duas pré-imagens com o mesmo valor função hash e também se a saída da função não depende explicitamente da entrada.

Vamos formular os requisitos básicos para funções de hash criptográficas:

  • a função hash deve ser aplicável a mensagens de qualquer tamanho;
  • o cálculo do valor da função deve ser feito com rapidez suficiente;
  • com um valor conhecido da função hash, deve ser difícil (quase impossível) encontrar uma pré-imagem adequada de M;
  • com uma mensagem M conhecida, deve ser difícil encontrar outra mensagem M 'com o mesmo valor hash da mensagem original;
  • deve ser difícil encontrar qualquer par de mensagens diferentes aleatórias com o mesmo valor de hash.

Criar uma função hash que atenda a todos esses requisitos não é uma tarefa fácil. Também deve ser lembrado que dados de tamanho arbitrário são recebidos na entrada da função, e o resultado do hash não deve ser o mesmo para dados de tamanhos diferentes.

Atualmente, na prática, as funções são usadas como funções hash que processam a mensagem de entrada bloco a bloco e calculam o valor de hash h i para cada bloco M i da mensagem de entrada de acordo com as dependências do formulário

h i = H (M i, h i-1),

onde h i-1 é o resultado obtido ao calcular a função hash para bloco anterior dados de entrada.

Como resultado, a saída da função hash h n é uma função de todos os n blocos da mensagem de entrada.

Usando algoritmos de criptografia de bloco para gerar uma função hash

Você pode usar uma função de hash de bloco como uma função de hash. Se o algoritmo de bloco usado for criptograficamente seguro, a função hash baseada nele será confiável.

A maneira mais simples de usar o algoritmo de bloco para obter um código hash é criptografar a mensagem no modo CBC. Nesse caso, a mensagem é apresentada como uma sequência de blocos, cujo comprimento é igual ao comprimento do bloco do algoritmo de criptografia. Se necessário, o último bloco é preenchido à direita com zeros para obter um bloco do comprimento desejado. O valor do hash será o último bloco de texto criptografado. Desde que um algoritmo de criptografia de bloco confiável seja usado, o valor de hash resultante terá as seguintes propriedades:

  • é quase impossível calcular o valor de hash para um determinado array aberto de informações sem saber a chave de criptografia;
  • é praticamente impossível selecionar dados abertos para um determinado valor de função hash sem saber a chave de criptografia.

O valor hash formado desta forma é geralmente chamado inserção de imitação ou autenticador e é usado para verificar a integridade da mensagem. Assim, a inserção de imitação é uma combinação de controle que depende de dados abertos e informações de chave secreta. O objetivo de usar uma inserção simulada é detectar todas as alterações acidentais ou deliberadas na matriz de informações. O valor obtido pela função hash ao processar uma mensagem de entrada é anexado à mensagem quando a mensagem é sabidamente correta. O destinatário verifica a integridade da mensagem calculando a personificação da mensagem recebida e comparando-a com o código hash recebido, que deve ser transmitido de maneira segura. Um desses caminhos seguros pode haver uma inserção de imitação de criptografia chave privada o remetente, ou seja, criando uma assinatura. Também é possível criptografar o código hash recebido com um algoritmo de criptografia simétrica, se o remetente e o receptor tiverem uma chave de criptografia simétrica comum.

O processo especificado para obter e usar uma inserção simulada é descrito no padrão doméstico GOST 28147-89. O padrão propõe o uso dos 32 bits menos significativos do bloco recebido na saída da operação de criptografia de toda a mensagem no modo de concatenação de blocos de criptografia para controlar a integridade da mensagem transmitida. Da mesma forma, para formar uma inserção de imitação, você pode usar qualquer bloco algoritmo de criptografia simétrica.

Outros maneira possível A aplicação de uma cifra de bloco para gerar um código hash é a seguinte. A mensagem original é processada sequencialmente em blocos. O último bloco é preenchido com zeros se necessário; às vezes, o comprimento da mensagem é adicionado ao último bloco como um número binário. Em cada estágio, criptografamos o valor de hash obtido no estágio anterior, tomando o bloco de mensagem atual como a chave. O último valor criptografado recebido será o resultado hash final.

Na verdade, existem vários outros esquemas possíveis para usar uma cifra de bloco para formar uma função hash. Seja М i o bloco da mensagem original, hi - o valor da função hash no i-ésimo estágio, f - o algoritmo de criptografia de bloco usado no modo de substituição simples, - a operação do módulo de adição 2. Então, para exemplo, os seguintes esquemas para gerar a função hash são possíveis:

Em todos esses esquemas, o comprimento do valor de hash gerado é igual ao comprimento do bloco criptografado. Todos esses, bem como alguns outros esquemas para usar um algoritmo de cifra de bloco para calcular valores de hash, podem ser aplicados na prática.

A principal desvantagem das funções hash projetadas com base em algoritmos de bloco é a relativa baixa velocidade trabalhar. A força criptográfica necessária pode ser alcançada em um número menor de operações nos dados de entrada. Existem algoritmos de hash mais rápidos, projetados de forma independente, do zero, com base nos requisitos de força criptográfica (os mais comuns deles são MD5, SHA-1, SHA-2 e GOST R 34.11-94).


O que é um hash? Uma função hash é uma transformação matemática de informações em uma string curta e específica.

Por que isso é necessário? A análise de hash costuma ser usada para verificar a integridade de arquivos importantes. sistema operacional, programas importantes, dados importantes. O controle pode ser realizado conforme necessário e regularmente.

Como isso é feito? Primeiro, eles determinam a integridade de quais arquivos precisam ser monitorados. Para cada arquivo, seu valor de hash é calculado usando um algoritmo especial, com o resultado sendo salvo. Após o tempo necessário, um cálculo semelhante é feito e os resultados são comparados. Se os valores forem diferentes, as informações contidas no arquivo foram alteradas.

Quais características uma função hash deve ter?

  • deve ser capaz de converter dados de comprimento arbitrário em fixo;
  • deve ter um algoritmo aberto para que você possa investigar sua força criptográfica;
  • deve ser unilateral, ou seja, não deve haver possibilidade matemática de determinar os dados iniciais pelo resultado;
  • deve "resistir" a colisões, ou seja, não deve produzir os mesmos valores para dados de entrada diferentes;
  • não deve exigir grandes recursos de computação;
  • à menor mudança nos dados de entrada, o resultado deve mudar significativamente.

Quais são os algoritmos de hash populares? As seguintes funções hash estão em uso:

  • CRC - Código de Redundância Cíclica ou Soma de Verificação. O algoritmo é bastante simples, tem um grande número de variações dependendo do comprimento de saída necessário. Não criptográfico!
  • MD 5 é um algoritmo muito popular. Como ele versão anterior MD 4 é uma função criptográfica. O tamanho do hash é 128 bits.
  • SHA -1 também é uma função criptográfica muito popular. O tamanho do hash é 160 bits.
  • GOST R 34.11-94 é um padrão criptográfico russo para calcular uma função hash. O tamanho do hash é de 256 bits.

Quando um administrador do sistema pode usar esses algoritmos? Muitas vezes, ao baixar qualquer conteúdo, por exemplo, programas do site do fabricante, músicas, filmes ou outras informações, há um valor somas de verificação calculado de acordo com um certo algoritmo. Por motivos de segurança, após o download, você precisa calcular de forma independente a função hash e comparar o valor com o que está indicado no site ou no anexo do arquivo. Você alguma vez já fez isso?

Por que é mais conveniente calcular o hash? Agora, há um grande número desses utilitários, tanto pagos quanto gratuitos. Eu pessoalmente gostei do HashTab. Em primeiro lugar, durante a instalação, o utilitário é integrado como uma guia nas propriedades do arquivo, em segundo lugar, permite que você escolha um grande número de algoritmos de hash e, em terceiro lugar, é gratuito para uso privado não comercial.

O que é russo? Conforme mencionado acima, na Rússia existe um padrão de hash GOST R 34.11-94, que é amplamente utilizado por muitos fabricantes de produtos de segurança da informação. Uma dessas ferramentas é o programa de fixação e controle do estado inicial. pacote de software CONSERTAR. Este programa é um meio de monitorar a eficácia do uso dos sistemas de segurança da informação.

FIX (versão 2.0.1) para Windows 9x / NT / 2000 / XP

  • Cálculo de checksums de arquivos especificados usando um dos 5 algoritmos implementados.
  • Fixação e controle subsequente do estado inicial do pacote de software.
  • Comparação de versões do pacote de software.
  • Fixação e controle de diretórios.
  • Controle de mudanças em arquivos especificados (diretórios).
  • Formação de relatórios nos formatos TXT, HTML, SV.
  • O produto tem um certificado FSTEC para NDV 3 No. 913 até 01 de junho de 2013.

E quanto à assinatura digital? O resultado do cálculo da função hash, juntamente com a chave secreta do usuário, vai para a entrada do algoritmo criptográfico, onde é calculada a assinatura digital. A rigor, a função hash não faz parte do algoritmo EDS, mas geralmente é feita propositalmente para excluir um ataque usando uma chave pública.

Hoje em dia, muitos aplicativos de e-commerce permitem armazenar a chave secreta do usuário em uma área de token privada (ruToken, eToken) sem a possibilidade técnica de extraí-la de lá. O token em si tem uma área de memória muito limitada, medida em kilobytes. Para assinar um documento, não há como transferir o documento para o token em si, mas é muito fácil transferir o hash do documento para o token e receber um EDS na saída.

Perguntas:

1. O conceito de uma função hash.

2. Uso de algoritmos de criptografia de bloco para formar uma função hash.

3. Revisão de algoritmos para a formação de funções hash.

1. Conceito de função Hash

Função Hash(função hash) é uma função matemática ou outra que, para uma string de comprimento arbitrário, calcula algum valor inteiro ou alguma outra string de comprimento fixo. Matematicamente, pode ser escrito assim:

h = H (M) ,

Onde M - a mensagem original, às vezes chamada protótipo , uma h - o resultado, chamado de valor da função hash (e também código hash ou mensagens de resumo(do inglês. resumo da mensagem)).

O significado da função hash é determinar o recurso característico da pré-imagem - o valor da função hash. Esse valor geralmente tem um determinado tamanho fixo, como 64 ou 128 bits. O código hash pode ser analisado posteriormente para resolver qualquer problema. Assim, por exemplo, o hashing pode ser usado para comparar dados: se dois arrays de dados têm códigos hash diferentes, os arrays são certamente diferentes; se eles forem iguais, os arrays provavelmente serão os mesmos. No caso geral, não há correspondência um a um entre os dados originais e o código hash devido ao fato de que o número de valores das funções hash é sempre menor que o número de variantes dos dados de entrada. Portanto, há muitas mensagens de entrada com os mesmos códigos hash (tais situações são chamadas colisões ) A probabilidade de colisões desempenha um papel importante na avaliação da qualidade das funções hash.

As funções de hash são amplamente utilizadas na criptografia moderna.

A função hash mais simples pode ser construída usando a operação "sum modulo 2" da seguinte maneira: obtemos a string de entrada, adicionamos todos os bytes módulo 2 e retornamos o byte de resultado como o valor da função hash. O comprimento do valor hash, neste caso, será de 8 bits, independentemente do tamanho da mensagem de entrada.

Por exemplo, suponha que a mensagem digitalizada original fosse a seguinte (em formato hexadecimal):

2 B1 4 A9 5 FE4

Vamos traduzir a mensagem para a forma binária, escrever os bytes um sobre o outro e adicionar os bits em cada módulo de coluna 2:

0010 1011

0001 0100

1010 1001

0101 1111

1110 0100

——————-

0010 1101

Resultado: 0010 1101 ou 2 D e será o valor da função hash.

No entanto, essa função hash não pode ser usada para fins criptográficos, por exemplo, para gerar uma assinatura eletrônica, uma vez que é muito fácil alterar o conteúdo de uma mensagem assinada sem alterar o valor do checksum.

Portanto, a função hash considerada não é adequada para aplicações criptográficas. Na criptografia, uma função hash é considerada boa se for difícil criar duas pré-imagens com o mesmo valor hash e também se a saída da função não depender explicitamente da entrada.

Vamos formular os requisitos básicos para funções de hash criptográficas:

· A função hash deve ser aplicável a uma mensagem de qualquer tamanho;

· O cálculo do valor da função deve ser realizado com rapidez suficiente;

Com um valor conhecido da função hash, deve ser difícil (quase impossível) encontrar uma pré-imagem adequada M ;

Com uma mensagem conhecida M deve ser difícil encontrar outra mensagem M ' com o mesmo valor hash da mensagem original;

· Deve ser difícil encontrar qualquer par de mensagens diferentes aleatórias com o mesmo valor de hash.

Criar uma função hash que atenda a todos esses requisitos não é uma tarefa fácil. Também deve ser lembrado que dados de tamanho arbitrário são recebidos na entrada da função, e o resultado do hash não deve ser o mesmo para dados de tamanhos diferentes.

Atualmente, na prática, as funções são usadas como funções hash que processam a mensagem de entrada bloco a bloco e calculam o valor hash Oi para cada bloco M i mensagem de entrada por dependências do formulário

h i = H (M i, h i-1),

Onde h i-1 - o resultado obtido ao calcular a função hash para o bloco anterior de dados de entrada.

Como resultado, a saída da função hash é h n é uma função de todos n blocos da mensagem de entrada.

2. Usando algoritmos de criptografia de bloco para gerar uma função hash.

Um algoritmo de criptografia de bloco simétrico pode ser usado como uma função hash. Se o algoritmo de bloco usado for criptograficamente seguro, a função hash baseada nele será confiável.

A maneira mais simples de usar o algoritmo de bloco para obter um código hash é criptografar a mensagem no modo CBC ( Cipher Block Chaining - Blocos de encadeamento de texto cifrado) Nesse caso, a mensagem é apresentada como uma sequência de blocos, cujo comprimento é igual ao comprimento do bloco do algoritmo de criptografia. Se necessário, o último bloco é preenchido à direita com zeros para obter um bloco do comprimento desejado. O valor do hash será o último bloco de texto criptografado. Desde que um algoritmo de criptografia de bloco confiável seja usado, o valor de hash resultante terá as seguintes propriedades:

· É praticamente impossível, sem o conhecimento da chave de criptografia, calcular o valor do hash para um determinado array aberto de informações;

· É praticamente impossível selecionar dados abertos para um determinado valor da função hash sem saber a chave de criptografia.

O valor hash formado desta forma é geralmente chamado inserção de imitação ou autenticador e é usado para verificar a integridade da mensagem. Assim, a inserção de imitação é uma combinação de controle que depende de dados abertos e informações de chave secreta. O objetivo de usar uma inserção simulada é detectar todas as alterações acidentais ou deliberadas na matriz de informações. O valor obtido pela função hash ao processar uma mensagem de entrada é anexado à mensagem quando a mensagem é sabidamente correta. O destinatário verifica a integridade da mensagem calculando a personificação da mensagem recebida e comparando-a com o código hash recebido, que deve ser transmitido de maneira segura. Um desses métodos seguros pode ser a criptografia da representação com a chave privada do remetente, ou seja, criando uma assinatura. Também é possível criptografar o código hash recebido com um algoritmo de criptografia simétrica, se o remetente e o receptor tiverem uma chave de criptografia simétrica comum.

O processo especificado para obter e usar uma inserção simulada é descrito no padrão doméstico GOST 28147-89. O padrão propõe o uso dos 32 bits menos significativos do bloco recebido na saída da operação de criptografia de toda a mensagem no modo de concatenação de blocos de criptografia para controlar a integridade da mensagem transmitida. Da mesma forma, qualquer algoritmo de criptografia simétrica de bloco pode ser usado para gerar uma inserção imitada.

Outra maneira possível de usar uma cifra de bloco para gerar um código hash é a seguinte. A mensagem original é processada sequencialmente em blocos. O último bloco é preenchido com zeros se necessário; às vezes, o comprimento da mensagem é adicionado ao último bloco como um número binário. Em cada estágio, criptografamos o valor de hash obtido no estágio anterior, tomando o bloco de mensagem atual como a chave. O último valor criptografado recebido será o resultado hash final.

Assim, se o esquema usual de criptografia de mensagens for M usando uma cifra de bloco f na chave PARA nós gravamos como E = f (M, K) , então o esquema para obter o código hash h de acordo com o algoritmo acima pode ser representado como

Oi = f ( Oi -1 , M )

Como o código hash inicial h 0 tome alguma constante. A criptografia é executada em um modo de substituição simples. Usando este método o tamanho do bloco é igual ao comprimento da chave e o tamanho do valor hash será o comprimento do bloco.

Outra forma de usar a cifra de bloco no modo de substituição simples também é possível: os elementos da mensagem são criptografados com os valores de hash obtidos na etapa anterior:

Oi = f ( M , Oi -1 ,)

Na verdade, existem vários outros esquemas possíveis para usar uma cifra de bloco para formar uma função hash. Deixe ser M i - bloco da mensagem original, Oi - o valor da função hash em eu -ésimo estágio, f - algoritmo de criptografia de bloco usado no modo de substituição simples; - módulo de operação de adição 2. Então, por exemplo, os seguintes esquemas para gerar uma função hash são possíveis:

Em todos esses esquemas, o comprimento do valor de hash gerado é igual ao comprimento do bloco criptografado. Todos esses, bem como alguns outros esquemas para usar um algoritmo de cifra de bloco para calcular valores de hash, podem ser aplicados na prática.

A principal desvantagem das funções hash projetadas com base em algoritmos de bloco é a velocidade de operação relativamente baixa. A força criptográfica necessária pode ser alcançada em um número menor de operações nos dados de entrada. Existem algoritmos de hash mais rápidos (os mais comuns deles são MD5, SHA-1, SHA-2 e GOST R 34.11-94).

3. Revisão de algoritmos para a formação de funções hash.

Atualmente, vários algoritmos especiais para o cálculo da função hash foram propostos e são usados ​​na prática. Os algoritmos mais famosos são MD5, SHA-1, SHA-2 e outras versões de SHA, bem como o algoritmo doméstico estabelecido em GOST R 34.11-94.

Algoritmo MD5 apareceu no início dos anos 90 do século XX como resultado do aprimoramento do algoritmo para gerar a função hash MD4. Os caracteres no nome "MD" representam Message Digest - um resumo da mensagem. O autor dos algoritmos MD4 e MD5 é R. Rivest. Como resultado do uso de MD5, um valor hash de 128 bits é gerado para uma mensagem arbitrária. Os dados de entrada são processados ​​em blocos de 512 bits. O algoritmo usa elementar operações lógicas(inversão, conjunção, módulo de adição 2, deslocamentos cíclicos, etc.), bem como adição aritmética ordinária. Repetição complexa destes funções elementares o algoritmo garante que o resultado após o processamento seja bem misturado. Portanto, é improvável que duas mensagens escolhidas aleatoriamente tenham o mesmo código hash. O algoritmo MD5 possui a seguinte propriedade: cada bit do valor hash recebido é uma função de cada bit da entrada. MD5 é considerado a função de hash mais forte para um valor de hash de 128 bits.

Algoritmo SHA O Secure Hash Algorithm (Secure Hash Algorithm) foi desenvolvido pelo Instituto Nacional de Padrões e Tecnologia dos Estados Unidos (NIST) e publicado como um Padrão de Informação Federal dos Estados Unidos em 1993. SHA-1, como MD5, é baseado no algoritmo MD4. SHA-1 gera um valor hash de 160 bits com base no processamento da mensagem original em blocos de 512 bits. O algoritmo SHA-1 também usa operações lógicas e aritméticas simples. A diferença mais importante entre SHA-1 e MD5 é que o hash SHA-1 é 32 bits mais longo do que o hash MD5. Se assumirmos que ambos os algoritmos têm a mesma complexidade para a criptoanálise, então o SHA-1 é um algoritmo mais robusto. Usando um ataque de força bruta (ataque frontal), é mais difícil criar uma mensagem arbitrária com um determinado código hash e também é mais difícil criar duas mensagens com o mesmo código hash.

Em 2001, o Instituto Nacional de Padrões e Tecnologia dos Estados Unidos adotou três funções de hash como padrão com um comprimento de hash maior do que SHA-1. Essas funções hash são freqüentemente chamadas de SHA-2 ou SHA-256, SHA-384 e SHA-512 (o nome indica o comprimento do código hash gerado pelos algoritmos). Esses algoritmos diferem não apenas no comprimento do código hash gerado, mas também nas funções internas usadas e no comprimento do bloco processado (para SHA-256, o comprimento do bloco é 512, e para SHA-384 e SHA-512, o comprimento do bloco é de 1024 bits). Melhorias graduais no algoritmo SHA levam a um aumento em sua força criptográfica. Apesar das diferenças entre os algoritmos considerados, todos eles são um desenvolvimento posterior do SHA-1 e MD4 e têm uma estrutura semelhante.

Na Rússia, foi adotado o GOST R34.11-94, que é o padrão doméstico para funções hash. Sua estrutura é bem diferente da estrutura dos algoritmos SHA-1,2 ou MD5, que são baseados no algoritmo MD4. O comprimento do código hash gerado pelo algoritmo GOST R 34.11-94 é de 256 bits. O algoritmo processa sequencialmente a mensagem original em blocos de 256 bits da direita para a esquerda. O parâmetro do algoritmo é o vetor de início de hash - um valor fixo arbitrário com comprimento de também 256 bits. O algoritmo GOST R 34.11-94 usa as operações de permutação, deslocamento, adição aritmética, módulo de adição 2. Como função auxiliar no GOST 34.11-94, o algoritmo de acordo com o GOST 28147-89 é usado no modo de substituição simples.

4. Requisitos de hash

Uma função hash é uma função unilateral projetada para recuperar um resumo ou "impressão digital" de um arquivo, mensagem ou algum bloco de dados.

O código hash é gerado pela função H :

h = H (M)

Onde M é uma mensagem de comprimento arbitrário e h é um código hash de comprimento fixo.

Considere os requisitos que uma função hash deve atender para ser usada como um autenticador de mensagem. Vejamos um exemplo muito simples de uma função hash. Em seguida, analisaremos várias abordagens para construir uma função hash.

Função Hash H usado para autenticar mensagens deve ter as seguintes propriedades:

1. Função Hash H deve ser aplicado a um bloco de dados de qualquer comprimento.

2. Função Hash H cria uma saída de comprimento fixo.

3. H (M) relativamente fácil (em tempo polinomial) é calculado para qualquer valor M .

4. Para qualquer valor de código hash fornecido h computacionalmente impossível de encontrar M de tal modo que H (M) = h .

5. Para qualquer dado NS é computacionalmente impossível descobrir que

H (y) = H (x).

6. É computacionalmente impossível encontrar um par arbitrário ( NS , y ) de tal modo que H (y) = H (x) .

As três primeiras propriedades requerem a função hash para gerar um código hash para qualquer mensagem.

A quarta propriedade define o requisito da função hash unilateral: é fácil criar um código hash a partir de uma determinada mensagem, mas é impossível recuperar uma mensagem de um determinado código hash. Esta propriedade é importante se a autenticação hash incluir um valor secreto. O valor secreto em si não pode ser enviado; no entanto, se a função hash não for unilateral, o adversário pode revelar facilmente o valor secreto da seguinte maneira. Quando a transmissão é interceptada, o invasor recebe uma mensagem M e código hash C = H (SAB || M) ... Se o invasor pode inverter a função hash, então, ele pode obter SAB || M = H-1 (C) ... Uma vez que o atacante agora sabe e M e SAB || M , receber SAB bem simples.

A quinta propriedade garante que nenhuma outra mensagem possa ser encontrada cujo valor de hash corresponda ao valor de hash. desta mensagem... Isso evita que o autenticador seja adulterado ao usar um código hash criptografado. Nesse caso, o adversário pode ler a mensagem e, portanto, gerar seu código hash. Mas, como o adversário não possui a chave secreta, ele não tem como alterar a mensagem para que o destinatário não a encontre. Se esta propriedade não for atendida, o invasor pode executar a seguinte sequência de ações: interceptar a mensagem e seu código hash criptografado, calcular o código hash da mensagem, criar uma mensagem alternativa com o mesmo código hash, substituir a mensagem original por uma falsa . Como os códigos hash dessas mensagens são os mesmos, o destinatário não detectará o spoofing.

Uma função hash que satisfaça as cinco primeiras propriedades é chamada de função hash simples ou fraca. Se, além disso, a sexta propriedade for satisfeita, essa função é chamada de função hash forte. A sexta propriedade protege contra uma classe de ataques conhecida como ataque de aniversário.

5. Funções hash simples

Todas as funções hash são executadas da seguinte maneira. Um valor de entrada (mensagem, arquivo, etc.) é tratado como uma sequência n blocos de bits. O valor de entrada é processado sequencialmente bloco a bloco e criado m -bit valor do código hash.

Um dos exemplos mais simples de uma função hash é o XOR bit a bit de cada bloco:

C i - eu o bit do código hash, 1 <= i <= n .

k - número n blocos de bits de entrada.

b ij eu th bit em j o bloco.

Em seguida, toda a mensagem é criptografada, incluindo o código hash, no modo CBC para criar blocos criptografados Y1, Y2, ..., YN + 1. Por definição de SHS, temos:

Mas XN + 1 é o código hash:

Como os termos da igualdade anterior podem ser calculados em qualquer ordem, o código hash não será alterado se os blocos criptografados forem reorganizados.

O padrão original proposto pelo NIST usava XOR simples, que era aplicado a blocos de mensagens de 64 bits, então toda a mensagem era criptografada usando o modo CBC.

"O paradoxo do aniversário"

Antes de examinar as funções hash mais complexas, há um ataque específico às funções hash simples que precisa ser analisado.

O chamado "paradoxo do aniversário" é o seguinte. Suponha que o número de valores de saída da função hash seja H é igual a n ... Qual deve ser o numero k de modo que para um valor específico X e valores Y1, , Yk a probabilidade de que pelo menos um Yi satisfaça a igualdade

H (X) = H (Y)

seria maior que 0,5.

Para um Y a probabilidade de que H (X) = H (Y) , é igual a 1 / n .

Assim, a probabilidade de que , é igual a 1 - 1 / n .

Se você criar k valores, então a probabilidade de que nenhum deles irá corresponder é igual ao produto das probabilidades correspondentes a um valor, ou seja, (1 - 1 / n) k .

Portanto, a probabilidade de pelo menos uma correspondência é

1 - (1 - 1 / n) k

Assim, descobrimos que para m código hash de bits é o suficiente para escolher 2m-1 mensagens de modo que a probabilidade de coincidir com os códigos hash seja maior que 0,5.

Agora, considere o seguinte problema: denote P (n, k) a probabilidade de que em um conjunto de k elementos, cada um dos quais pode levar n valores, há pelo menos dois com os mesmos valores. O que deveria ser igual k , para P (n, k) seria mais 0,5 ?

O número de maneiras diferentes de selecionar elementos de forma que não haja duplicatas é

n (n-1) ... (n-k + 1) = n! / (n-k)!

O número total de maneiras possíveis de selecionar elementos é n k

A probabilidade de que não haja duplicatas é n! / (n-k)! n k

A probabilidade de haver duplicatas, respectivamente, é

1 - n! / (N-k)! Nk P (n, k) = 1 - n! / ((n-k)! x nk) = 1 - (n x (n-1) x ... x (n-k-1)) / nk = 1 - [(n-1) / n x (n-2) / n x ... x (n-k + 1) / n] = 1 - [(1- 1 / n) x (1 - 2 / n) x ... x (1 - (k-1) / n)]

Se o hashcode for longo m bit, ou seja, leva 2m valores, então

Esse resultado é chamado de "paradoxo do aniversário" porque, de acordo com o raciocínio acima, para que duas pessoas tenham mais de 0,5 probabilidade de coincidir os aniversários, deve haver apenas 23 pessoas em um grupo. Esse resultado parece surpreendente, talvez porque para cada indivíduo no grupo, a probabilidade de que seu aniversário coincida com o aniversário de outra pessoa do grupo é bastante pequena.

Vamos voltar a considerar as propriedades das funções hash. Vamos supor que um código hash de 64 bits seja usado. Pode-se considerar que este é um comprimento completamente suficiente e, portanto, seguro para o código hash. Por exemplo, se o código hash criptografado for COM transmitido com a mensagem não criptografada correspondente M , então o inimigo precisará encontrar M ' de tal modo que

H (M ") = H (M) ,

a fim de falsificar a mensagem e enganar o destinatário. Em média, o adversário deve passar por 263 mensagens para encontrar uma cujo código hash seja igual à mensagem interceptada.

No entanto, vários tipos de ataques são possíveis com base no "paradoxo do aniversário". A seguinte estratégia é possível:

1. O inimigo cria 2 m / 2 opções de mensagem, cada uma com um significado específico. O adversário prepara o mesmo número de mensagens, cada uma das quais falsa e destinada a substituir a mensagem real.

2. Os dois conjuntos de mensagens são comparados para localizar um par de mensagens com o mesmo código hash. A probabilidade de sucesso de acordo com o "paradoxo do aniversário" é maior que 0,5. Se nenhum par correspondente for encontrado, as mensagens originais e falsas adicionais serão geradas até que uma correspondência seja encontrada.

3. O invasor oferece ao remetente a mensagem original para assinatura. Essa assinatura pode então ser anexada à variante falsa para transmissão ao destinatário. Como as duas opções têm o mesmo hashcode, a mesma assinatura será gerada. O adversário terá certeza do sucesso, mesmo sem conhecer a chave de criptografia.

Assim, se um código hash de 64 bits for usado, a complexidade computacional necessária é da ordem de 232.

Em conclusão, notamos que o comprimento do código hash deve ser grande o suficiente. Um comprimento de 64 bits não é considerado seguro no momento. De preferência, o comprimento é da ordem de 100 bits.

Usando encadeamento de blocos criptografados

Existem várias funções hash baseadas na criação de uma cadeia de blocos criptografados, mas sem o uso de uma chave secreta. Uma dessas funções hash foi proposta por Rabin. Mensagem M quebrado em blocos de comprimento fixo M1, M2 ,. ... ... , МN e usa um algoritmo de criptografia simétrico como DES para calcular o código hash G Da seguinte maneira:

H 0 - valor inicial Oi = E Mi G = H N

Isso é semelhante ao uso de criptografia no modo CBC, mas neste caso não há uma chave secreta. Como acontece com qualquer função hash simples, esse algoritmo é suscetível a um "ataque de aniversário" e, se o algoritmo de criptografia for DES e apenas um código hash de 64 bits for gerado, o sistema é considerado bastante vulnerável.

Outros ataques, como "aniversário" podem ser realizados, os quais são possíveis mesmo que o adversário tenha acesso a apenas uma mensagem e ao código hash criptografado correspondente e não consiga obter vários pares de mensagens e códigos hash criptografados. O seguinte cenário é possível: suponha que o adversário interceptou a mensagem com o autenticador na forma de um código hash criptografado, e é sabido que o código hash não criptografado tem um comprimento m bits. Em seguida, o inimigo deve realizar as seguintes ações:

Usando o algoritmo descrito acima, calcule o código hash não criptografado G .

Crie uma mensagem falsa como Q1, Q2 ,. ... ... , QN-2 .

Calcular H i = E Qi para 1 <= i <= N-2 .

· Crio 2 m / 2 blocos aleatórios NS e para cada um desses blocos NS calcular E X ... Crie adicionais 2 m / 2 bloco aleatório Y e para cada bloco Y calcular D Y [G] , Onde D - função de descriptografia correspondente E ... Com base no "paradoxo do aniversário", podemos dizer que com alto grau de probabilidade esta sequência conterá blocos NS e Y de tal modo que E X = D Y [Y] .

Criar mensagem Q1, Q2 ,. ... ... , QN-2, X, Y ... Esta mensagem contém um código hash G e, portanto, pode ser usado em conjunto com um autenticador criptografado.

Essa forma de ataque é conhecida como ataque meet-in-the-middle. Vários estudos propuseram métodos mais sofisticados para fortalecer a abordagem do blockchain. Por exemplo, Davis e Price descreveram a seguinte opção:

Outra opção é possível:

No entanto, ambos os esquemas também são vulneráveis ​​a vários ataques. De forma mais geral, alguma forma de "ataque de aniversário" pode ter sucesso com qualquer algoritmo de hash envolvendo o uso de um blockchain de cifra sem o uso de uma chave secreta.

Pesquisas adicionais foram direcionadas para encontrar outras abordagens para a criação de funções de hashing.

Função Hash MD5

Considere o algoritmo de resumo de mensagem MD5 (RFC 1321) desenvolvido por Ron Rivest do MIT.

Lógica de execução MD5

O algoritmo recebe uma mensagem de comprimento arbitrário como entrada e cria um resumo da mensagem de 128 bits como saída. O algoritmo consiste nas seguintes etapas:

Arroz. 8,1 Lógica de execução MD5

Etapa 1: adicionar bits ausentes

A mensagem é complementada de forma que seu comprimento se torne 448 módulo 512 (). Isso significa que o comprimento da mensagem adicionada é 64 bits menor que um múltiplo de 512. A adição é sempre feita, mesmo que a mensagem tenha o comprimento necessário. Por exemplo, se uma mensagem tiver 448 bits, ela será preenchida com 512 bits a 960 bits. Assim, o número de bits adicionados varia de 1 a 512.

A adição consiste em um seguido pelo número necessário de zeros.

Etapa 2: adicionar comprimento

A representação de 64 bits do comprimento da mensagem original (antes de anexar) em bits é anexada ao resultado da primeira etapa. Se o comprimento original for maior que 2 64, apenas os últimos 64 bits serão usados. Assim, o campo contém o comprimento do módulo 64 da mensagem original.

As duas primeiras etapas criam uma mensagem que é um múltiplo de 512 bits. Esta mensagem estendida é representada como uma sequência de blocos Y 0, Y 1, de 512 bits. ... ., Y L-1, enquanto o comprimento total da mensagem estendida é L * 512 bits. Assim, o comprimento da mensagem estendida recebida é um múltiplo de dezesseis palavras de 32 bits.

Arroz. 8,2. Estrutura de mensagem estendida

Etapa 3: inicializar o buffer MD

Um buffer de 128 bits é usado para armazenar os resultados intermediários e finais da função hash. O buffer pode ser representado como quatro registradores de 32 bits (A, B, C, D). Esses registros são inicializados com os seguintes números hexadecimais:

A = 01234567 B = 89ABCDEF C = FEDCBA98 D = 76543210

Etapa 4: processamento de uma sequência de blocos de 512 bits (16 palavras)

A base do algoritmo é um módulo composto por quatro processamentos cíclicos, denominados HMD5. Os quatro loops têm uma estrutura semelhante, mas cada loop usa sua própria função lógica atômica, denotada por f F, f G, f H e f I, respectivamente.

Arroz. 8,3. Processando o próximo bloco de 512 bits

Cada ciclo recebe como entrada o bloco Y q atual de 512 bits, que está sendo processado no momento, e o valor de 128 bits do buffer ABCD, que é um valor intermediário do resumo, e altera o conteúdo desse buffer. Cada loop também usa a quarta parte da tabela T de 64 elementos com base na função sin. O i-ésimo elemento de T, denotado por T [i], tem um valor igual à parte inteira de 2 32 * abs (sin (i)), i está em radianos. Como abs (sin (i)) é um número entre 0 e 1, cada elemento de T é um inteiro que pode ser representado por 32 bits. A tabela fornece um conjunto "aleatório" de valores de 32 bits que deve eliminar qualquer regularidade na entrada.

Para obter MD q + 1, a saída de quatro ciclos é adicionada ao módulo 2 32 com MD q. A adição é realizada independentemente para cada uma das quatro palavras do buffer.

CLS s - deslocamento circular à esquerda em s bits de um argumento de 32 bits.

X [k] - M é a k-ésima palavra de 32 bits no q-ésimo bloco de mensagem 512.

T [i] - i-ésima palavra de 32 bits na matriz T.

+ - mod de adição 2 32.

Em cada um dos quatro ciclos do algoritmo, uma das quatro funções lógicas elementares é usada. Cada função atômica usa três palavras de 32 bits como entrada e cria uma palavra de 32 bits na saída. Cada função é um conjunto de operações lógicas bit a bit, ou seja, O enésimo bit da saída é uma função do enésimo bit das três entradas. As funções elementares são as seguintes:

Uma matriz de palavras X de 32 bits contém o valor do bloco de entrada de 512 bits atual que está sendo processado. Cada ciclo é executado 16 vezes e, uma vez que cada bloco da mensagem de entrada é processado em quatro ciclos, cada bloco da mensagem de entrada é processado de acordo com o esquema mostrado na Fig. 4, 64 vezes. Se representarmos o bloco de 512 bits de entrada na forma de dezesseis palavras de 32 bits, então cada palavra de 32 bits de entrada é usada quatro vezes, uma vez em cada ciclo, e cada elemento da tabela T, consistindo em 64 palavras de 32 bits palavras, é usado apenas uma vez. Após cada etapa do loop, quatro palavras A, B, C e D são ciclicamente deslocadas para a esquerda. Em cada etapa, apenas uma das quatro palavras do buffer ABCD é alterada. Portanto, cada palavra no buffer é alterada 16 vezes e, em seguida, a 17ª vez no final para obter a saída final desse bloco.

digerir.

2. Velocidade: a implementação do algoritmo no software deve ser rápida o suficiente. Em particular, o algoritmo deve ser rápido o suficiente em uma arquitetura de 32 bits. Portanto, o algoritmo é baseado em um conjunto simples de operações elementares em palavras de 32 bits.

3. Simplicidade e compactação: o algoritmo deve ser simples de descrever e fácil de programar, sem grandes programas ou tabelas de consulta. Essas características não só apresentam vantagens óbvias de software, mas também são desejáveis ​​do ponto de vista da segurança, pois é melhor ter um algoritmo simples para analisar possíveis fragilidades.

4. Arquitetura little-endian desejável: algumas arquiteturas de processador (como a linha Intel 80xxx) armazenam os bytes do lado esquerdo da palavra na posição dos endereços de byte menos significativos (little-endian). Outros (como SUN Sparcstation) armazenam os bytes certos da palavra na posição dos endereços de byte menos significativos (grande constante extra MD4 no primeiro loop não se aplica. Uma constante extra semelhante é usada para cada uma das etapas no segundo Outra constante adicional é usada para cada uma das etapas no terceiro loop. O código hash é uma função de cada bit da entrada. A repetição complexa das funções atômicas f F, f G, f H e f I garante que o resultado é bem misturado; isto é, é improvável que duas mensagens sejam escolhidas aleatoriamente, mesmo que tenham padrões aparentemente semelhantes, tenham os mesmos resumos que produzem o mesmo valor de saída, o que significa que executar MD5 em um único bloco de 512 bits resultará na mesma saída para duas entradas diferentes no buffer ABCD. não existe no MD5.