Funções hash estão em toda parte no desenvolvimento de software, mesmo que você não perceba. Toda vez que você baixa um arquivo e verifica o checksum, faz login em um site, cria um commit no Git ou minera Bitcoin (tá, talvez essa última não), funções hash estão fazendo o trabalho pesado nos bastidores.
Vamos entender o que são, como diferem e quando usar cada uma.
O que é uma função hash?
Uma função hash recebe qualquer entrada — um único caractere, um romance, um arquivo de vídeo de 4 GB — e produz uma saída de tamanho fixo chamada "hash" ou "digest". Pense nela como uma impressão digital para dados. Não importa quão grande ou pequena a entrada seja, a saída sempre tem o mesmo comprimento.
Veja o que torna as funções hash especiais:
- Determinística: A mesma entrada sempre gera a mesma saída.
hash("hello")retornará o mesmo valor toda vez, em qualquer máquina, em qualquer linguagem de programação. - Unidirecional: Não é possível reverter a entrada a partir da saída. Dado um hash, não há como descobrir o que o produziu (além de adivinhar). É isso que as torna úteis para armazenar senhas.
- Efeito avalanche: Mude um bit da entrada e a saída muda drasticamente. Por exemplo, o hash SHA-256 de "hello" e "Hello" são strings completamente diferentes.
- Resistente a colisões: Deve ser praticamente impossível encontrar duas entradas diferentes que produzam a mesma saída hash. Quanto mais forte o algoritmo, mais difícil é encontrar colisões.
Veja na prática:
Saídas totalmente diferentes de entradas que diferem por apenas um caractere — um "h" minúsculo versus um "H" maiúsculo. Isso é o efeito avalanche em ação.
Como as funções hash funcionam por dentro
Embora a matemática por trás das funções hash seja complexa, o processo geral é direto. A maioria das funções hash segue estes passos:
1. Padding: A mensagem de entrada é preenchida para que seu comprimento se torne um múltiplo de um tamanho de bloco fixo (por exemplo, 512 bits para SHA-256). Isso garante que o algoritmo possa processar os dados em blocos uniformes.
2. Divisão em blocos: A mensagem preenchida é dividida em blocos de tamanho fixo.
3. Rodadas de compressão: Cada bloco é processado através de múltiplas rodadas de operações bit a bit — XOR, AND, OR, deslocamentos de bits e adição modular. Essas operações misturam os bits completamente para que cada bit de saída dependa de cada bit de entrada.
4. Encadeamento: A saída do processamento de um bloco alimenta o processamento do próximo bloco. É por isso que mesmo uma pequena mudança no início da entrada se propaga por todos os blocos subsequentes.
5. Digest final: Depois que todos os blocos são processados, o estado interno é gerado como o valor hash final.
O insight chave é que essas operações são fáceis de computar na direção direta, mas praticamente impossíveis de reverter. Você não pode "desmisturar" os bits para recuperar a entrada original.
MD5: O veterano aposentado
MD5 foi projetado por Ronald Rivest em 1991 e produz um hash de 128 bits (32 caracteres hexadecimais). Foi o padrão por décadas — você via checksums MD5 ao lado de cada download de arquivo na internet.
No entanto, MD5 agora é considerado criptograficamente quebrado. Pesquisadores demonstraram ataques de colisão práticos — ou seja, conseguem criar dois arquivos diferentes que produzem o mesmo hash MD5. Em 2008, pesquisadores usaram uma colisão MD5 para criar um certificado SSL fraudulento, provando que não era apenas uma fraqueza teórica.
Ainda aceitável para: verificação de integridade de arquivos (confirmar que um download foi concluído corretamente), checksums para deduplicação, tabelas hash não relacionadas a segurança e fingerprinting rápido de dados onde segurança não é uma preocupação.
Nunca usar para: armazenamento de senhas, assinaturas digitais, certificados de segurança, ou qualquer coisa onde alguém possa deliberadamente criar colisões.
SHA-1: Também aposentado
SHA-1 foi projetado pela NSA e publicado em 1995. Produz um hash de 160 bits (40 caracteres hexadecimais). Por anos foi o padrão em certificados SSL, assinaturas PGP e sistemas de controle de versão.
Foi descontinuado depois que o Google demonstrou um ataque de colisão prático em 2017 (o famoso ataque SHAttered). Eles criaram dois arquivos PDF diferentes com o mesmo hash SHA-1. O ataque exigiu 9.223.372.036.854.775.808 computações SHA-1 — enorme, mas viável com recursos modernos de computação em nuvem.
O Git ainda usa SHA-1 internamente para hashes de commits, mas está migrando para SHA-256. Navegadores e autoridades certificadoras pararam de aceitar certificados SHA-1 há anos. Se você encontrar SHA-1 sendo usado para segurança em qualquer base de código hoje, deve ser sinalizado e migrado.
SHA-256 e SHA-512: Os padrões atuais
Estes fazem parte da família SHA-2, projetada pela NSA e publicada em 2001. São o que você deveria estar usando hoje para a maioria dos propósitos.
- SHA-256: Saída de 256 bits (64 caracteres hex). Usado no Bitcoin, certificados TLS e na maioria das aplicações de segurança. É o equilíbrio perfeito entre segurança e desempenho. Todo o sistema de prova de trabalho do Bitcoin é construído sobre hashing duplo SHA-256.
- SHA-512: Saída de 512 bits (128 caracteres hex). Saída maior significa mais resistência a colisões. Curiosamente, SHA-512 geralmente é mais rápido que SHA-256 em processadores de 64 bits porque opera nativamente com palavras de 64 bits, enquanto SHA-256 usa palavras de 32 bits.
- SHA-384 e SHA-512/256: São variantes truncadas do SHA-512. SHA-384 fornece uma saída de 384 bits, enquanto SHA-512/256 fornece uma saída de 256 bits, mas com os benefícios de desempenho das operações de 64 bits do SHA-512.
Comparação rápida:
SHA-3: A próxima geração
SHA-3 foi padronizado em 2015 após uma competição pública conduzida pelo NIST. Diferente do SHA-2, que usa uma construção Merkle-Damgard, o SHA-3 é baseado na construção esponja Keccak — um design fundamentalmente diferente.
Por que isso importa? Se um avanço matemático algum dia comprometer a abordagem de design do SHA-2, o SHA-3 não será afetado porque funciona de maneira completamente diferente. É uma apólice de seguro para a comunidade criptográfica.
SHA-3 vem nos mesmos tamanhos de saída — SHA3-256, SHA3-384, SHA3-512 — e também introduz SHAKE128 e SHAKE256, que são "funções de saída extensível" que podem produzir um hash de qualquer comprimento desejado.
Na prática, SHA-2 ainda é mais amplamente utilizado e mais rápido na maioria do hardware. A adoção do SHA-3 está crescendo, mas é mais um padrão de backup do que uma substituição.
Casos de uso no mundo real
Controle de versão Git: Cada commit, árvore e blob no Git é identificado pelo seu hash SHA-1. Quando você executa git commit, o Git faz hash do conteúdo das suas alterações, da estrutura da árvore, do hash do commit pai, das suas informações de autor e do timestamp. É por isso que hashes de commit parecem a1b2c3d4e5f6... — são literalmente digests SHA-1.
Mineração de Bitcoin: Mineradores competem para encontrar um valor nonce que, quando combinado com os dados do bloco e hashado com duplo SHA-256, produz um hash abaixo de um limiar alvo. A dificuldade de encontrar esse hash é o que protege toda a rede. Em 2024, a rede Bitcoin computa aproximadamente 500 quintilhões de hashes SHA-256 por segundo.
Deduplicação de arquivos: Serviços de armazenamento em nuvem como o Dropbox fazem hash de cada arquivo que você envia. Se o hash corresponder a um arquivo existente, eles não armazenam uma duplicata — apenas adicionam um ponteiro. Isso economiza enormes quantidades de armazenamento.
Assinaturas digitais: Quando você assina um documento ou uma versão de software, não está assinando o arquivo inteiro. Em vez disso, o arquivo é hashado, e o hash é o que é assinado com sua chave privada. O destinatário faz hash do arquivo por conta própria e verifica a assinatura contra esse hash.
Autenticação de API: HMAC (Código de Autenticação de Mensagem baseado em Hash) combina uma chave secreta com um hash de mensagem para verificar tanto a integridade quanto a autenticidade das requisições de API. AWS, Stripe e a maioria das APIs principais usam HMAC-SHA256 para assinatura de requisições.
Erros comuns que desenvolvedores cometem com hashing
Usar funções hash para senhas: SHA-256 puro é rápido demais para hashing de senhas. Um atacante com uma GPU pode computar bilhões de hashes SHA-256 por segundo, tornando ataques de força bruta triviais. Sempre use funções de hashing de senha dedicadas como bcrypt, scrypt ou Argon2, que são intencionalmente lentas e intensivas em memória.
Não usar salt: Se você faz hash de senhas sem um salt (um valor aleatório adicionado a cada senha antes do hashing), senhas idênticas produzem hashes idênticos. Um atacante com uma "rainbow table" pré-computada pode encontrar senhas comuns instantaneamente. Sempre adicione um salt único e aleatório por usuário.
Comparar hashes de forma insegura quanto ao timing: Usar == para comparar hashes em código sensível a segurança pode vazar informações através de canais laterais de timing. Um atacante pode medir quanto tempo a comparação leva e deduzir o hash caractere por caractere. Use funções de comparação em tempo constante como crypto.timingSafeEqual() no Node.js ou hmac.compare_digest() no Python.
Truncar hashes: Alguns desenvolvedores truncam hashes para economizar espaço (por exemplo, armazenando apenas os primeiros 16 caracteres de um hash SHA-256). Isso reduz dramaticamente a resistência a colisões. Um hash SHA-256 completo tem 2^256 valores possíveis; truncar para 16 caracteres hexadecimais deixa apenas 2^64 — um número que hardware moderno pode quebrar por força bruta.
Qual função hash usar?
- Integridade de arquivos (sem segurança): SHA-256 ou até MD5 serve. Você está verificando corrupção acidental, não adulteração maliciosa.
- Armazenamento de senhas: Nenhuma dessas! Use bcrypt, scrypt ou Argon2 — são deliberadamente lentos, o que torna ataques de força bruta impraticáveis. Funções hash regulares são rápidas demais para hashing de senhas.
- Assinaturas digitais e certificados: SHA-256 ou SHA-512.
- HMAC (autenticação de mensagens): SHA-256 ou SHA-512.
- Endereçamento de conteúdo estilo Git: SHA-256 (para onde o Git está caminhando).
- Preparação para o futuro: Se você está construindo um sistema que precisa durar décadas e quer um plano de contingência caso o SHA-2 seja comprometido, considere o SHA-3.
- Checksums em pipelines de dados: SHA-256 para verificação de integridade de dados entre estágios do pipeline. CRC32 é mais rápido, mas só detecta erros acidentais, não adulteração intencional.
Funções hash no código: Exemplos práticos
Tá, chega de teoria — vamos escrever um pouco de código. Porque, honestamente, a melhor forma de entender hashing é simplesmente... fazer. Veja como computar hashes nas linguagens que você provavelmente usa todo dia.
Node.js — O módulo crypto embutido torna isso super simples:
E a parte legal — fazer hash de um arquivo é quase a mesma coisa:
Python — O hashlib do Python é igualmente direto. Eu acho que o Python tem a API mais bonita pra isso:
Go — A biblioteca padrão do Go é incrivelmente bem projetada para isso:
Java — Um pouco mais verboso (porque... Java), mas funciona muito bem:
Verificando um download de arquivo: Este é um dos usos mais práticos do hashing. Digamos que você baixou uma ISO do Linux e o site diz que o checksum SHA-256 deveria ser abc123.... Veja como verificar:
Eu sei que isso parece básico, mas você ficaria surpreso com quantos desenvolvedores pulam esse passo. Um único byte corrompido em um download de 4 GB pode estragar sua tarde inteira.
Rainbow Tables e por que são aterrorizantes
Tá, essa é a parte que me explodiu a cabeça quando aprendi pela primeira vez. Imagina que alguém pré-computa o hash para toda senha possível de até, digamos, 8 caracteres. Toda combinação de letras, números e símbolos. Eles armazenam todos esses mapeamentos hash-para-senha em uma tabela de busca gigante.
Isso é uma rainbow table. E elas são absolutamente aterrorizantes.
O motivo: se você armazenou senhas como hashes SHA-256 puros (sem salt), um atacante que obtém seu banco de dados não precisa "quebrar" nada. Ele simplesmente consulta cada hash na rainbow table dele. Pronto — recuperação instantânea de senhas. A consulta leva microssegundos.
Quão grandes são essas tabelas? Uma rainbow table cobrindo todas as senhas alfanuméricas de até 8 caracteres pode ter cerca de 100-200 GB. Parece muito, mas cabe em um único SSD. Sites como CrackStation têm tabelas com bilhões de hashes pré-computados e quebram hashes de senhas comuns em segundos de graça.
Agora a boa notícia: o salting derrota completamente as rainbow tables. Um salt é apenas uma string aleatória que você adiciona à senha antes de fazer o hash:
Viu o que aconteceu? A mesma senha ("password123") produz hashes completamente diferentes por causa dos salts diferentes. Um atacante precisaria construir uma rainbow table separada para cada salt possível, o que é computacionalmente impossível.
Toda biblioteca moderna de hashing de senhas (bcrypt, Argon2, scrypt) lida com o salting automaticamente. Se você algum dia sentir a tentação de criar seu próprio hashing de senhas — não faça isso. Sério. Use bcrypt e siga com sua vida.
HMAC: Hashing com um segredo
HMAC significa Código de Autenticação de Mensagem baseado em Hash, e eu sei, eu sei, isso soa intimidador. Mas fica comigo — na verdade é um conceito bem simples que você provavelmente já usou sem perceber.
Hashing regular pega uma mensagem e produz um hash. HMAC pega uma mensagem E uma chave secreta, e produz um hash. A diferença chave (trocadilho intencional) é que apenas alguém que conhece a chave secreta pode produzir ou verificar o HMAC. Ele prova duas coisas de uma vez: a mensagem não foi adulterada, E veio de alguém que conhece o segredo.
Onde você vê isso no mundo real? Assinaturas de webhooks. Quando o GitHub ou o Stripe envia um webhook para seu servidor, eles incluem uma assinatura HMAC-SHA256 nos headers. Seu servidor pode verificar que o webhook realmente veio do GitHub (e não foi falsificado por algum atacante aleatório) computando o HMAC você mesmo e comparando.
Aqui está um exemplo prático de verificação de uma assinatura de webhook do GitHub em Node.js:
Reparou na chamada timingSafeEqual? Isso é crucial. Uma comparação regular com === retorna false assim que encontra o primeiro caractere diferente, o que significa que um atacante pode medir o tempo de resposta e descobrir a assinatura byte por byte. A comparação segura quanto ao timing sempre leva o mesmo tempo, independente de onde a diferença ocorre.
Benchmarks de desempenho de funções hash
Olha, eu entendo — desempenho importa. Especialmente se você está fazendo hash de milhões de arquivos em um pipeline de build ou processando uma avalanche de dados. Então veja como as principais funções hash se comparam em velocidade (benchmarks aproximados em hardware x86_64 moderno):
Espera, você notou isso? BLAKE3 é 10x mais rápido que SHA-256 sendo criptograficamente seguro. Não é erro de digitação.
BLAKE3 é a novidade mais quente no mundo do hashing, e por boas razões. Ele é baseado na família BLAKE2 (que já superou o SHA-3 na competição do NIST) mas redesenhado para aproveitar paralelismo SIMD e multithreading. Ele consegue fazer hash de dados basicamente na velocidade do memcpy.
Por que você deveria se importar? Ferramentas de build se importam. E muito. Ferramentas como Bazel, Buck e vários sistemas de armazenamento endereçados por conteúdo passam uma quantidade surpreendente de tempo fazendo hash de arquivos. Trocar de SHA-256 para BLAKE3 pode acelerar a verificação de dependências em uma ordem de magnitude. O ecossistema Rust vem adotando BLAKE3 agressivamente, e ele está aparecendo em cada vez mais lugares.
Dito isso, SHA-256 e SHA-512 continuam sendo a escolha certa quando você precisa de ampla compatibilidade ou conformidade com padrões como FIPS. Nem tudo suporta BLAKE3 ainda, e em muitos casos de uso, a velocidade do hashing não é o gargalo de qualquer forma.
Blockchain e árvores de Merkle: Hashing em escala
Tá, aqui é onde fica realmente legal. Sabe como o Git consegue dizer exatamente qual arquivo mudou em um repositório gigante? E como o Bitcoin consegue verificar uma transação sem baixar a blockchain inteira? O segredo é uma estrutura de dados chamada árvore de Merkle (batizada em homenagem a Ralph Merkle, que a patenteou em 1979).
Uma árvore de Merkle é basicamente uma árvore de hashes. Funciona assim — imagine que você tem quatro blocos de dados:
Cada nó folha é o hash de um bloco de dados. Cada nó pai é o hash dos seus dois filhos concatenados. O hash raiz (às vezes chamado de "Merkle root") é um único hash que representa TODOS os dados na árvore.
Aqui está a parte genuinamente elegante: se mesmo um bit do Data C mudar, Hash(C) muda, o que significa que Hash(CD) muda, o que significa que o Root Hash muda. Você pode detectar adulteração instantaneamente verificando apenas a raiz.
Mas fica melhor. Digamos que você quer provar que Data C faz parte da árvore sem revelar Data A, B ou D. Você só precisa fornecer: Data C, Hash(D) e Hash(AB). O verificador pode reconstruir o caminho até a raiz e conferir se bate. Isso é chamado de "prova de Merkle", e é incrivelmente eficiente — para uma árvore com um milhão de folhas, a prova tem apenas cerca de 20 hashes de comprimento (log2 de 1.000.000).
Onde isso é usado na prática?
- Git: Seu repositório inteiro é uma árvore de Merkle. Commits apontam para trees, trees apontam para blobs, e tudo é identificado pelo seu hash SHA-1. É por isso que o Git consegue saber instantaneamente se algo mudou.
- Bitcoin: Cada bloco contém uma Merkle root de todas as transações. Clientes leves (como carteiras mobile) podem verificar uma transação específica usando uma prova de Merkle sem baixar o bloco inteiro.
- IPFS: O InterPlanetary File System divide arquivos em chunks, constrói um Merkle DAG (grafo acíclico dirigido) e usa o hash raiz como identificador de conteúdo (CID) do arquivo.
- Certificate Transparency: Os logs de Certificate Transparency do Google usam árvores de Merkle para que qualquer pessoa possa verificar eficientemente se um certificado foi (ou não) registrado.
O futuro: Funções hash pós-quânticas
Você já deve ter ouvido que computadores quânticos vão quebrar toda nossa criptografia. E sim, isso é parcialmente verdade — RSA, ECC e Diffie-Hellman estão todos condenados quando computadores quânticos de larga escala chegarem. O algoritmo de Shor consegue fatorar números grandes e computar logaritmos discretos eficientemente, que é do que esses sistemas dependem.
Mas aqui está a notícia surpreendentemente boa: funções hash são na verdade bem seguras contra computadores quânticos. A principal ameaça quântica às funções hash é o algoritmo de Grover, que consegue buscar em um espaço não-estruturado quadraticamente mais rápido. Na prática, isso significa que ele reduz pela metade os bits de segurança — SHA-256 vai de 2^256 para 2^128 de resistência contra ataques quânticos.
2^128 ainda é absolutamente enorme. Isso é aproximadamente o número de átomos no universo observável ao quadrado. Ninguém vai quebrar isso por força bruta, computador quântico ou não.
Então, enquanto o NIST está trabalhando ativamente em padrões de criptografia pós-quântica (e finalizou vários em 2024), a urgência está principalmente na criptografia de chave pública e assinaturas — não nas funções hash. Se você está usando SHA-256 hoje, pode dormir tranquilo sabendo que computadores quânticos não vão torná-lo inútil.
Dito isso, se você é verdadeiramente paranoico (e em criptografia, paranoia é uma virtude), subir para SHA-512 ou SHA3-256 dá uma margem de segurança extra. Alguns esquemas de assinatura pós-quânticos como SPHINCS+ são construídos inteiramente sobre funções hash, o que é um belo voto de confiança na resistência quântica delas.
Colisões de hash: Ataques de aniversário explicados
Vamos falar sobre uma das coisas mais contra-intuitivas em toda a ciência da computação: o ataque de aniversário. Ele leva esse nome por causa do paradoxo do aniversário, e é a razão pela qual funções hash precisam ser maiores do que você intuitivamente esperaria.
O paradoxo do aniversário: em uma sala com apenas 23 pessoas, há 50% de chance de que duas delas façam aniversário no mesmo dia. Não um aniversário específico — qualquer par que coincida. Com 70 pessoas, a probabilidade pula para 99,9%. A maioria das pessoas chutaria que você precisaria de umas 183 pessoas (metade de 365), mas o número real é muito menor porque estamos procurando QUALQUER colisão, não uma específica.
Exatamente a mesma matemática se aplica a funções hash. Se uma função hash produz N saídas possíveis, você não precisa computar N hashes para encontrar uma colisão — você só precisa de aproximadamente a raiz quadrada de N.
Para um hash de 256 bits como SHA-256, existem 2^256 saídas possíveis. Encontrar uma colisão requer aproximadamente 2^128 operações (a raiz quadrada de 2^256). Isso ainda é um número impossivelmente grande — mas é a razão pela qual não podemos simplesmente usar um hash de 64 bits e pronto.
É exatamente por isso que MD5 (128 bits) desmoronou. Sua resistência a colisões era apenas 2^64 desde o início, e fraquezas estruturais no algoritmo a reduziram ainda mais. Pesquisadores eventualmente encontraram colisões em segundos em um laptop comum.
A conclusão prática? Sempre use no mínimo uma função hash de 256 bits para qualquer coisa relacionada a segurança. SHA-256, SHA3-256 ou BLAKE3 são todas excelentes escolhas. E se alguém sugerir usar um hash de 64 ou 128 bits para propósitos de segurança, agora você sabe exatamente por que isso é uma ideia terrível.
Experimente você mesmo
Curioso para ver o hash dos seus dados? Use nosso Gerador de hash MD5, Gerador de hash SHA-256 ou Gerador de hash SHA-512. Cole um texto e veja como até mudanças mínimas produzem hashes completamente diferentes — é a melhor maneira de construir intuição sobre como esses algoritmos se comportam.