domingo, 2 de dezembro de 2012

Algoritmos de Hash Unidirecionais para Senhas: III - Ataques

Quais Ataques? 

 

No terceiro artigo da série, vamos falar sobre os riscos de ataque a algoritmos de hash.
Estamos falando de algoritmos de hash unidirecionais. Este tipo de algoritmo, diferente dos sistemas criptográficos tradicionais, não possui um algoritmo inverso para obter a mensagem original.
Imagine uma folha de papel A4 simples. Agora vamos dobrar a folha repetidamente até ela ficar com 5x5 cm. Se desdobrarmos o papel seguindo a ordem inversa, vamos obter a folha A4 novamente. O ato de dobrar o papel repetidamente pode representar um algoritmo criptográfico tradicional. Embora o papel tenha assumido outra forma, o seu conteúdo não se perdeu e por isso é possível recuperá-lo com um "algoritmo" inverso. No caso dos algoritmos de hash, se quiséssemos pegar uma folha A4 e transformá-la em um pedaço de 5x5 cm, nós simplesmente a cortaríamos para ficar deste tamanho. É um procedimento mais prático e com um resultado melhor, no entanto, não será mais possível obter a folha original, já que o conteúdo do papel não está mais totalmente contido nele. Agora, se não é possível obter a mensagem original com um algoritmo de hash, então como podem existir ataques?
Como vimos nos artigos anteriores, o objetivo dos algoritmos de hash é mapear uma determinada mensagem para um determinado hash. Este hash pode ser considerado único para aquela mensagem e portanto, pode substituí-la para processos de autenticação sem revelar a mensagem original.
Seguindo este raciocínio, se nós pegarmos a mensagem "senha123" e calcularmos o seu hash com o algoritmo MD5, por exemplo, vamos obter o seguinte resultado:
e7d80ffeefa212b7c5c55700e4f7193e
Se o objetivo destes algoritmos é criar hashes únicos, então podemos admitir que o hash acima será obtido sempre para o valor "senha123". Desta forma, o hash acima não é mais sigiloso.
Portanto, os ataques nestes algoritmos se baseiam na tentativa de reproduzir o mesmo hash.

 

Força Bruta 

 

Quase qualquer algoritmo criptográfico está sujeito a um ataque de Força Bruta. Este ataque consiste basicamente de tentativa e erro, onde um algoritmo varre todo o espaço de strings possíveis até encontrar uma que produza o hash alvo, ou grupo de hashes alvo. Embora seja uma técnica "garantida", sua eficiência é baixa devido ao tempo necessário para se obter algum resultado. Em algoritmos modernos seu uso só se torna possível com a exploração de falhas.

 

Dictionary Attack

 

O Dictionary Attack (preciso traduzir?) é semelhante ao Força Bruta, com a diferença de que neste ataque o espaço de strings possíveis é reduzido para possibilidades reais, como palavras que existem em um dicionário por exemplo. Este ataque é mais rápido e costuma ser mais eficiente.
Quando o attacker consegue acesso a um banco de dados com os hashes das senhas, ele pode alimentar o dicionário com strings de senhas comuns (existem listas das senhas mais comuns disponíveis online ou para download). Se o banco de dados contiver um número razoável de hashes, há uma boa probabilidade de que várias combinações sejam encontradas. Para isso, um algoritmo gera os hashes de todas as strings do dicionário primeiro, assim basta combinar os hashes do dicionário com os do banco de dados e em caso de combinação, o attacker saberá qual a string que gerou aquele hash. Em pouco tempo ele obterá diversas senhas de usuários daquele sistema.

 

Dois Inimigos: Evolução Tecnológica e Colisões

 

Os ataques acima podem conseguir bons resultados em uma base de dados grande. Mas se o objetivo for quebrar um único hash específico, tudo se torna mais difícil. Mas talvez não difícil o suficiente. Ataques de exaustão como os dois mencionados precisam realizar bilhões de tentativas para obter sucesso. Obviamente, quanto melhor o desempenho do computador, mais rápido o ataque será executado e portanto mais eficiente será. Por isso, um dos maiores inimigos dos algoritmos de hash é o desenvolvimento da capacidade computacional. Os novos algoritmos são projetados para serem mais difíceis de computar.
Outro fator que joga contra um algoritmo de hash são as colisões. Já falamos delas no segundo artigo da série.
Acontece que se você quer quebrar um hash, você não precisa encontrar A string que o produziu, você precisa encontrar UMA string que o produza. Ou seja, você precisa encontrar uma colisão. Isso aumenta consideravelmente a probabilidade de sucesso.
Observe este exemplo de um código para encontrar colisões no algoritmo CRC-32 em Python:
#Encontra colisões no algoritmo CRC-32 - Jonathan Hepp

#Python 3

from zlib import crc32
from random import random

def crc(s):
    return crc32(bytes(s, "utf-8"))

s1 = str(random())
alvo = crc(s1)
contador = 0
while 1:
    s2 = str(random())
    mh = crc(s2)
    if mh == alvo and s1 != s2:
        print()
        print("Colisão encontrada após {} tentativas:".format(contador))
        print(s2)
        print(alvo)
        print(s1)
        print(mh)
        break
    contador += 1


O código acima é apenas um exemplo para este artigo e não é eficiente para uso prático, mas encontrou uma colisão entre as strings: "0.8011699626652646" e "0.5071682873120356" em 1476850939 tentativas (aproximadamente 1 hora em um QuadCore 4GB).
É claro que em algoritmos como MD5, SHA-1 e SHA-256 o processo é mais complexo e demorado, mas ainda assim são passíveis de ataques.
Mas note que estes ataques só são possíveis em construções de sistemas falhos. É importante ressaltar que um sistema bem construído não está sujeito a ataques como os abordados por este artigo.
No próximo artigo vamos falar sobre um tipo de construção de hash para senhas que facilita o trabalho de attackers.

Nenhum comentário:

Postar um comentário