sexta-feira, 17 de outubro de 2008

Um papo sobre algoritmos criptográficos de hashing, parte 1

Algoritmos criptográficos de hashing são a grande base das assinaturas digitais hoje em dia.



Um algoritmo de hash criptográfico, codifica uma tabela de dados sendo essa tabela/vetor um texto ou qualquer outro tipo de dado.

Essa codigicação dá-se através de cálculos matemáticos e no final do "hashing", não importa o tamanho do dado codigicado, ele terá um tamanho prédefinido, sempre.



De fato, na teoria, como temos um tamanho fixo na saída do algorítmo criptográfico, o que um hash deveria fazer seria evitar colisões(quando duas entradas diferentes geram um mesmo hash).

Mas justamente pelo fato de termos um número de possibilidades de saída limitado, evitar colisões fica impossível mas o que realmente um desenvolvedor de um algoritmo criptográfico de hashing quer, é que essas colisões sejam impossíveis de se achar intencionalmente.



Um dos "hash algorithms" mais utilizados é/foi o hash MD5 feito em 1992 substituindo o seu predecessor MD4.

Os hashs são famosos por serem "one-way" ou seja, você pode encriptar um determinado dado mas é impossível de desencriptá-lo.



Então o que torna um algoritmo de escrutínio(hashing) seguro ou inseguro?



São exatamente essas tais de colisões...

Quando podemos achá-las intencionalmente as possibilidades se tornam inimagináveis já que hashs são usados por exemplo, para verificar a integridade e autenticidade de um executavel.



A um tempo atrás, um exemplo de ataque em que dois executaveis tinham a mesma assinatura de hashing foi publicado.

Nele, apesar de ambos os arquivos terem a mesma assinatura de hash, um era comum e outro extremamente malévolo.



Desde 1994 técnicas para encontrar colisões no MD5 vem sendo largamente descobertas e publicadas, até que em 2004 o MD5 foi quebrado.



Nesse contexto nós temos uma grande falha de segurança por falta de algoritmos de escrutínio descentes.



Ronald L. Rivest então apresenta seu mais novo hash, o MD6.



Podendo usar até mais processadores para diminuir a demora para gerar um hash.

Um algoritmo robusto e complexo o que teoricamente o torna seguro.

O problema é que ele é inviável.

Sua complexidade o torna dificil de ser implementado e principalmente, o torna caro e nada sustentável.



Então o que nós vamos fazer ao longo desse "papo sobre hashing" é dar um pontapé em um algoritmo de hashing mais viável e poderoso.



Eu gosto de me impor desafios para aumentar as minhas habilidades e essa coleção de artigos será uma aula tanto para o leitor quanto para o autor.


Bem primeiro vamos entender como a operação lógica xor(exclusive or) é usada em algoritmos criptográficos e porquê.



As operações lógicas são operações baseadas na lógica binária, ou é certo ou é errado.



O verdadeiro é representado pelo número 1 e o que é falso pelo número 0.



Na programação isso é comum.



if(szTexto == "ola" || szTexto == "Ola")

{

 Bloco de comandos

}



A operação acima é uma operação or("ou" em português).

se a variável szTexto for igual a "ola" ou "Ola" então o que a operação lógica or faz é retornar verdadeiro(1) se uma das duas condições avaliadas ou ambos forem verdadeiras.



A exclusive or é quase a mesma coisa com o diferencial de que ela só retorna verdadeiro se um

dos dois valores for verdadeiro, e se ambos forem verdadeiros, ele retorna falso.



Vamos dar uma olhada na table dos possíveis valores e resultados:

A B Resultado

0 0   0

0 1    1

1 0    1

1 1    0



Quando ambos são falsos, o resultado da operação é falso, quando ambos são verdadeiro, resultado falso também.. ou seja, operandos iguais resultado falso.



É interessante notar que:



x = a xor b

a = x xor b

b = x xor a



Vejamos a veracidade dessa informação:



a = 101

b = 100



101 xor 100 = 001



Para fazer a conta simplesmente coloque um valor abaixo do outro:

1 0 1

1 0 0

____

0 0 1



Logo, x = 1;



Bem, vamos achar A só sabendo o valor de b...



B(100) xor X(1) = A(101)



Vamos descobrir B apartir de A agora...



A(101) xor x(001) = B(100)



O interessante é perceber que, o resultado de uma operação XOR entre dois valores, pode sempre ser usado para descobrir um valor.



Ou seja, se tivermos uma string e usarmos uma operação xor nela com um determinado valor, o resultado será uma string totalmente diferente, mas usando o tal valor usado no xor e essa string totalmente diferente, o resultado da operação será aquela string do começo.



Defato, o valor usado na operação xor com a string comum pode ser usado como chave para descobrir essa string após ser encriptada.



Vamos ver um exemplo disso em C...


#include <stdio.h>



int main()
{
 char szStr[] = "sergio";
 int iKey = 0x9;

 printf("String: %s\n", szStr);


 for(int iNum = 0; szStr[iNum]; ++iNum)
  szStr[iNum] = szStr[iNum] ^ iKey;

 printf("String encriptada: %s\n", szStr);


 for(int iNum = 0; szStr[iNum]; ++iNum)
  szStr[iNum] = szStr[iNum] ^ iKey;

 printf("String desencriptada: %s\n", szStr);

 return 0;
}



Ou seja, nós encriptamos uma string usando a operação lógica xor e uma chave(iKey) e depois à usamos para desencriptar a string.

Algoritmos de escrutínio costumam usar a mesma operação lógica, a diferença é que eles "jogam a chave fora" para que ninguem consiga descobrir a senha antiga... é claro que de uma forma mais complicada que conheceremos com o tempo aqui no blog. =]

Nenhum comentário:

Postar um comentário

><))).>