Criptografia
Criptografia, codificação e hash: diferenças explicadas
Separe codificação, criptografia e hash com laboratórios de Base64, SHA-256 e OpenSSL. Entenda por que Base64 não protege e o hash é de mão única.
Chaves pública e privada do zero: o que uma assinatura prova, por que a chave privada fica em segredo e um lab Ed25519 em que um arquivo alterado falha.
A criptografia de chave pública resolve um problema que segredos compartilhados não resolvem. Duas partes que nunca se viram podem combinar um segredo por uma rede que qualquer pessoa consegue ler. Alguém também pode provar que escreveu um arquivo, sem compartilhar nenhum segredo com quem confere. As duas coisas funcionam porque um par de chaves tem duas metades matematicamente ligadas. A metade pública pode ser compartilhada livremente. A metade privada fica com o dono.
Se você já usou TLS ou SSH, já dependeu dessa ideia. O cadeado do navegador e o aviso de impressão digital no cliente SSH dependem de chaves públicas. Este artigo explica a ideia por trás dessas ferramentas. Você vai gerar um par de chaves Ed25519 com o OpenSSL, assinar um arquivo, verificar a assinatura e, depois, alterar uma linha do arquivo e ver a verificação falhar. Releases de software assinados usam esse mesmo mecanismo, e ele é a base das chaves SSH que tratamos no próximo artigo.
Um par de chaves são dois valores gerados juntos. A chave privada é secreta. A chave pública pode ser copiada para um arquivo, enviada por e-mail ou colada na configuração de um servidor. As duas estão ligadas por uma matemática que funciona em um sentido só. Derivar a chave pública a partir da privada é fácil. Recuperar a chave privada a partir da pública é, por projeto, inviável.
Os criptógrafos chamam isso de criptografia assimétrica porque as duas partes usam chaves diferentes. A criptografia simétrica, como o AES, usa um segredo para as duas direções. Um esquema assimétrico divide o trabalho: uma chave executa uma operação, e a outra a desfaz ou a verifica.
Uma imagem simples ajuda. Pense na chave pública como uma fenda de correio. Qualquer pessoa pode colocar uma carta ali, e só o dono tem a chave da caixa. Nas assinaturas, os papéis se invertem. O dono carimba um documento com um selo que só ele consegue produzir, e qualquer pessoa com a referência pública pode conferir o selo. A imagem não é perfeita, porque chaves são números e não metal. O que vale guardar é o sentido de cada operação.
As pessoas misturam essas duas coisas o tempo todo, então vale separá-las.
Criptografar para uma chave pública permite que qualquer pessoa com a sua chave pública crie uma mensagem que só a sua chave privada abre. O objetivo é a confidencialidade. Qualquer um pode escrever para você, e só você pode ler o que foi escrito.
Uma assinatura é um valor que o detentor da chave privada calcula sobre alguns dados. Qualquer pessoa com a chave pública pode verificá-lo. O objetivo é a autenticidade e a integridade: os dados vieram do detentor da chave, e ninguém os alterou depois da assinatura. Uma assinatura não esconde nada. Um arquivo assinado continua totalmente legível para quem o tiver.
O Ed25519, algoritmo deste laboratório, é só um esquema de assinatura. Ele não consegue criptografar uma mensagem. A criptografia com chave pública usa outras construções, como o RSA-OAEP, ou um acordo de chaves em curva elíptica seguido de uma cifra simétrica. Por isso, "chave pública" nomeia uma forma de chave, não um único trabalho. Aprenda primeiro o trabalho e depois o algoritmo.
O TLS usa os dois trabalhos. Em um handshake TLS 1.3, o servidor assina dados com a chave privada correspondente à chave pública do certificado, o que mostra que ele controla essa chave. Um acordo de chaves separado deriva então chaves simétricas que criptografam a sessão. O SSH segue a mesma divisão. Uma chave de host assina parte do handshake, e uma chave de usuário assina um desafio de autenticação. Depois que você separa os dois trabalhos, os protocolos ficam mais fáceis de ler.
A chave privada é a prova. Quem a tem consegue produzir assinaturas que verificam como aquela identidade e consegue decifrar mensagens cifradas para ela. O verificador não vê seu rosto nem seu nome. Ele confere uma assinatura com a chave pública em que confia. Se alguém copiar a sua chave privada, todo verificador que confia na sua chave pública passa a aceitar a cópia como se fosse você.
Disso saem quatro regras práticas:
Compartilhar a chave pública é seguro por construção. O problema difícil é o outro sentido. O verificador precisa saber que uma chave pública realmente pertence à pessoa ou ao projeto que ela diz representar. Autoridades certificadoras, chaves de release fixadas e impressões digitais de host SSH existem para resolver exatamente isso. Uma assinatura válida prova que o detentor da chave assinou aqueles bytes. Ela não prova que o detentor é quem você esperava.
A sequência de um release assinado é esta:
Dois detalhes importam mais do que parecem. Primeiro, a assinatura cobre bytes exatos. Um espaço a mais, uma quebra de linha diferente ou um pacote recompactado é outra mensagem, e a verificação falha. Segundo, a assinatura Ed25519 da RFC 8032 é determinística. A mesma chave e a mesma mensagem sempre produzem a mesma assinatura, então assinar não precisa de um número aleatório novo a cada vez. Isso elimina uma fonte comum de falha. A FIPS 186-5 lista o EdDSA ao lado do ECDSA e do RSA como algoritmos de assinatura digital aprovados.
Você precisa do OpenSSL 3.x. A saída deste laboratório veio de um Windows 11 com Git Bash e OpenSSL 3.2.4. Confira sua versão com openssl version antes de começar. Versões antigas podem não aceitar as mesmas opções.
Use o Git Bash, um shell Linux ou o Terminal do macOS. O Windows PowerShell 5.1 grava UTF-16 quando você usa >, e isso muda os bytes que você assina. Execute o laboratório em uma pasta vazia da sua máquina. Ele não se conecta a nenhum servidor.
openssl genpkey -algorithm ed25519 -out priv.pem
openssl pkey -in priv.pem -pubout -out pub.pemOs dois comandos não imprimem nada quando dão certo. Confira o resultado:
ls -l priv.pem pub.pemNa nossa máquina, priv.pem tinha 122 bytes e pub.pem tinha 116 bytes. Abra o pub.pem e você verá o bloco -----BEGIN PUBLIC KEY-----. Não imprima nem cole o priv.pem. Neste laboratório ele é a metade secreta da identidade, e acostumar-se a nunca exibi-lo é um hábito que você também vai precisar com as chaves SSH.
echo 'release v1.0' > msg.txt
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out msg.sig
ls -l msg.sigA opção -rawin diz ao OpenSSL para assinar o conteúdo do arquivo diretamente. O Ed25519 calcula o hash da entrada dentro do próprio algoritmo, então você não calcula o hash do arquivo antes. O comando de assinatura não imprime nada. Nosso msg.sig tinha 64 bytes.
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in msg.txt -sigfile msg.sigSaída:
Signature Verified SuccessfullyA opção -pubin indica que o arquivo de chave tem só a metade pública. O verificador não precisa de mais nada, então esta checagem pode rodar em outra máquina que nunca viu o priv.pem.
echo 'release v1.1' > msg.txt
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in msg.txt -sigfile msg.sigSaída:
Signature Verification FailureO comando termina com status 1. A assinatura em msg.sig foi feita sobre release v1.0, então ela não pode garantir release v1.1. O OpenSSL não diz o que mudou. Ele informa apenas que os bytes e a assinatura não batem. Um verificador deve tratar esse resultado como uma parada obrigatória, não como um aviso para ignorar.
Estas checagens mostram exatamente o que a assinatura cobre. Rodamos cada uma na mesma máquina, na mesma pasta.
Assinar o mesmo arquivo duas vezes gera os mesmos bytes:
echo 'release v1.0' > msg.txt
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out a.sig
openssl pkeyutl -sign -inkey priv.pem -rawin -in msg.txt -out b.sig
cmp a.sig b.sig && echo "identical"Saída:
identicalUm arquivo sem a quebra de linha final falha. O echo adiciona uma quebra de linha, então este arquivo fica um byte menor:
printf 'release v1.0' > nonl.txt
openssl pkeyutl -verify -pubin -inkey pub.pem -rawin -in nonl.txt -sigfile a.sigSaída:
Signature Verification FailureO msg.txt tem 13 bytes e o nonl.txt tem 12. A assinatura foi feita sobre a versão de 13 bytes, então o arquivo mais curto falha.
Um par de chaves diferente também falha, mesmo com o arquivo original:
openssl genpkey -algorithm ed25519 -out other.pem
openssl pkey -in other.pem -pubout -out other.pub
openssl pkeyutl -verify -pubin -inkey other.pub -rawin -in msg.txt -sigfile a.sigSaída:
Signature Verification FailureO arquivo e a assinatura estão corretos. A chave pública pertence a outro par, então a checagem falha.
O próximo artigo, O que é SSH e como funcionam as chaves SSH?, usa essa mesma ideia de assinatura para autenticar logins. Se você tem arquivos id_ed25519, eles são um par exatamente desse tipo. Leia também o artigo sobre hash de senhas, porque senhas e chaves resolvem partes diferentes da autenticação, e vale mantê-las separadas na cabeça.
Para se aprofundar, leia a RFC 8032 para ver como o Ed25519 estrutura as assinaturas e por que ele é determinístico. Leia a FIPS 186-5 para entender onde o EdDSA se encaixa entre os algoritmos de assinatura aprovados. Leia as páginas de manual do genpkey e do pkeyutl do OpenSSL para conhecer as outras opções, como a escolha de digest em algoritmos que precisam dela. Depois, verifique um release de um projeto que você já usa, com a chave que o próprio projeto publica no site. Execute apenas arquivos baixados da fonte oficial e só depois de conferi-los.
As normas e as páginas de manual acima são as referências primárias deste artigo:
openssl-genpkeyopenssl-pkeyutlCriptografia
Separe codificação, criptografia e hash com laboratórios de Base64, SHA-256 e OpenSSL. Entenda por que Base64 não protege e o hash é de mão única.
Criptografia
Por que hashes rápidos falham em senhas, o que fazem o salt e o fator de trabalho, e como gerar e verificar hashes com Argon2id em Python.
Criptografia
Criptografia do zero: cifragem, chaves, hashes, assinaturas e por que a cifra de César ensina ideias, mas não protege nada.