Funções hash criptográficas
Uma função que transforma qualquer coisa — uma senha, um filme de 4 GB, um contrato — num número de tamanho fixo, de tal forma que é fácil calcular e inviável desfazer. Essa assimetria é o tijolo com que todo o resto desta trilha é construído: assinaturas, certificados, blockchains e a forma como sua senha é guardada.
A impressão digital de um dado
Uma impressão digital é minúscula perto de uma pessoa, mas identifica essa pessoa e só ela. E não existe caminho de volta: da impressão não se reconstrói o rosto, a voz ou o nome. Uma função hash faz exatamente isso com dados — 256 bits que identificam um arquivo de qualquer tamanho, sem guardar nada dele.
A analogia falha num ponto importante, e vale corrigi-la: impressões digitais parecidas pertencem a pessoas parecidas. Hashes, não. Trocar um único bit da entrada muda, em média, metade dos bits da saída — o efeito avalanche. Não existe "hash parecido", e é justamente por isso que nenhuma informação sobre a entrada sobrevive de forma explorável.
SHA-256 de verdade, rodando aqui
A primeira aba calcula SHA-256 real (os valores batem com sha256sum): mude uma letra e
conte quantos dos 256 bits viraram. Na segunda, um banco vazou — ative e desative o salt e tente
quebrar as senhas com uma tabela rainbow.
Uma função hash criptográfica transforma qualquer entrada — 3 letras ou 3 gigabytes — em 256 bits de tamanho fixo. Este é o SHA-256 de verdade, implementado em JavaScript: os valores abaixo batem com sha256sum e com hashlib.sha256.
1 por um 2, apague um acento — e compare os dois hashes.As três propriedades
# Uma função hash criptográfica precisa de três propriedades. # Cada uma corresponde a um ataque diferente: # # 1. RESISTÊNCIA À PRÉ-IMAGEM # Dado h, é inviável achar m tal que hash(m) = h. # "não dá para reverter o hash de uma senha" # # 2. RESISTÊNCIA À SEGUNDA PRÉ-IMAGEM # Dado m1, é inviável achar m2 ≠ m1 com o mesmo hash. # "não dá para trocar um contrato assinado por outro" # # 3. RESISTÊNCIA À COLISÃO # É inviável achar QUALQUER par m1 ≠ m2 com o mesmo hash. # "não dá para preparar dois contratos com o mesmo hash" # # A 3ª é a mais difícil de garantir por causa do PARADOXO DO # ANIVERSÁRIO: achar uma colisão qualquer custa ~2^(n/2), não 2^n. # Para SHA-256 isso ainda são 2^128 operações — inviável. # Mas para MD5 (128 bits) são 2^64, e hoje se faz em SEGUNDOS. import hashlib hashlib.sha256(b"mensagem").hexdigest() # sempre 64 caracteres hex = 256 bits, para QUALQUER entrada
O que está quebrado, e o que usar
# O cemitério: nenhum destes deve ser usado hoje para segurança. # # MD5 (1992) — colisões práticas desde 2004. Segundos num laptop. # SHA-1 (1995) — colisão real demonstrada em 2017 (ataque SHAttered: # dois PDFs diferentes com o MESMO SHA-1). Aposentado. # # SHA-256 / SHA-512 (família SHA-2) — o padrão atual, sólido. # SHA-3 (Keccak) — construção interna diferente, como reserva # estratégica caso o SHA-2 caia um dia. # BLAKE3 — moderno e muito rápido, paralelizável. # ⚠ MD5 ainda é ACEITÁVEL para o que não é segurança: # detectar arquivos duplicados, particionar dados, checksums # contra corrupção acidental de disco. O que ele não resiste é a # um ADVERSÁRIO tentando fabricar uma colisão de propósito. # Por que uma colisão é grave na prática? # Assinaturas digitais assinam o HASH do documento, não o documento. # Se eu consigo produzir dois arquivos com o mesmo hash, consigo # pedir sua assinatura no inofensivo e colar essa assinatura no outro.
Senhas: o caso em que rápido é ruim
# ✗ NUNCA: hash rápido para senhas hashlib.sha256(senha.encode()).hexdigest() # Uma GPU moderna calcula BILHÕES de SHA-256 por segundo. # Toda senha comum cai em minutos. # ✗ NUNCA: hash sem salt # Usuários com a mesma senha ficam com o mesmo hash — e uma # tabela pré-computada (rainbow table) quebra todos de uma vez. # ✓ SEMPRE: função lenta, com salt automático import bcrypt hash = bcrypt.hashpw(senha.encode(), bcrypt.gensalt(rounds=12)) # O salt já vai embutido no resultado; você não gerencia nada. bcrypt.checkpw(tentativa.encode(), hash) # → True/False # Ou Argon2id, o vencedor da Password Hashing Competition e a # recomendação atual do OWASP: from argon2 import PasswordHasher ph = PasswordHasher() # custo de tempo E de memória h = ph.hash(senha) ph.verify(h, tentativa) # Por que "lento" é uma VIRTUDE aqui? # Você verifica 1 senha por login: 100 ms não incomoda ninguém. # O atacante precisa testar bilhões: 100 ms cada inviabiliza tudo. # O custo de MEMÓRIA (scrypt, Argon2) é ainda mais importante: # é o que impede o atacante de rodar milhares de tentativas em # paralelo numa GPU, que tem muitos núcleos mas pouca memória cada. # E compare sempre em TEMPO CONSTANTE, para não vazar informação # pelo tempo de resposta: import hmac hmac.compare_digest(a, b) # não para no primeiro byte diferente
Onde mais o hash aparece
# Hash não é só para senhas. Onde mais ele aparece: # INTEGRIDADE — o arquivo baixado é o arquivo publicado? # sha256sum ubuntu.iso → compare com o publicado no site # HMAC — integridade COM autenticidade, usando chave compartilhada import hmac, hashlib assinatura = hmac.new(chave_secreta, corpo, hashlib.sha256).hexdigest() # É assim que webhooks (Stripe, GitHub) provam que a requisição # veio mesmo deles, e não de alguém que descobriu a URL. # ⚠ hash(chave + msg) NÃO é HMAC — essa construção ingênua sofre o # ataque de extensão de comprimento. Use a função hmac pronta. # IDENTIDADE POR CONTEÚDO — o hash VIRA o nome # Git: cada commit, árvore e blob é endereçado pelo seu hash. # Mudar qualquer byte do passado muda todos os hashes seguintes — # é isso que torna o histórico verificável. # IPFS, Docker (sha256:...), Nix: mesma ideia. # ENCADEAMENTO — cada bloco contém o hash do anterior # Blockchains e logs de auditoria à prova de adulteração: # alterar o passado exigiria recalcular tudo desde então. # DEDUPLICAÇÃO E PARTICIONAMENTO # Backups que guardam cada bloco uma única vez; sharding por hash.
Experimentando
Mini projeto: meça o efeito avalanche você mesmo. Gere 10 mil pares de strings que diferem em um único caractere, calcule o SHA-256 de cada uma e conte quantos bits mudam. Plote o histograma: ele deve se concentrar em torno de 128, com o formato de uma binomial. Depois repita com uma função hash ruim (a soma dos códigos dos caracteres, por exemplo) e compare os dois histogramas — a diferença é visualmente óbvia.
Projeto principal: implemente um log de auditoria à prova de adulteração. Cada
entrada guarda (dados, hash_anterior) e seu próprio hash cobre os dois campos. Escreva a
função verificar() que percorre a cadeia do início ao fim conferindo tudo. Depois altere
à mão uma entrada no meio do arquivo e mostre que a verificação aponta exatamente onde a
cadeia quebrou. Você acabou de construir o mecanismo central de uma blockchain — sem nenhuma
criptomoeda envolvida.
Desafio extra: demonstre a fraqueza do MD5 na prática. Baixe os dois arquivos do ataque SHAttered (SHA-1) ou um par de colisão de MD5 já publicado, confirme que os hashes são idênticos e que os arquivos não são, e depois confira que o SHA-256 dos dois difere. Em seguida implemente um ataque de força bruta a um hash truncado — use apenas os primeiros 4 bytes do SHA-256 e cronometre quanto leva para achar uma colisão. Multiplique mentalmente para 32 bytes e você terá a intuição de por que 256 bits basta.
Teste sua intuição
Onde você encontra isso
Git é um banco de dados endereçado por conteúdo
Cada commit, árvore e blob do Git tem como nome o hash do próprio conteúdo. Como o commit inclui o hash do commit pai, alterar qualquer coisa no passado muda todos os hashes seguintes — é por isso que um rebase reescreve os identificadores. O Git está migrando de SHA-1 para SHA-256 justamente por causa das colisões demonstradas em 2017.
Vazamentos e o que eles revelam
Quando um vazamento é noticiado, a diferença entre "as senhas estavam em MD5 sem salt" e "estavam em bcrypt" é a diferença entre milhões de contas quebradas em horas e um incidente sem consequência prática. O serviço Have I Been Pwned permite consultar se uma senha aparece em vazamentos usando k-anonimato: seu navegador envia só os 5 primeiros caracteres do hash SHA-1, e o servidor devolve todos os que começam assim — sem nunca saber qual era o seu.
Reprodutibilidade e cadeia de suprimentos
Docker identifica imagens por sha256:..., o npm e o pip guardam hashes de integridade no lockfile, e o Nix endereça pacotes pelo hash de todas as suas entradas. Tudo isso responde à mesma pergunta: "o artefato que estou instalando é exatamente o que foi publicado?" — a defesa básica contra ataques à cadeia de suprimentos.