Verifique a integridade de arquivos com Go e SHA256
Para verificar um arquivo em Go, envie seus bytes em streaming para sha256.New() com io.Copy e depois
compare o digest com um checksum obtido de uma fonte confiável. O programa de linha de comando abaixo
pode imprimir o hash de um arquivo ou verificar um hash esperado, com status de saída diferente de
zero quando a verificação falha.
Comece com um checksum confiável
O SHA256 produz um digest de 256 bits: 32 bytes, geralmente escritos como 64 caracteres
hexadecimais. A biblioteca padrão do Go o oferece por meio de crypto/sha256.
Um digest descreve os bytes do arquivo; ele não é uma assinatura digital nem uma garantia de que o
arquivo é seguro para executar.
Para um download, obtenha o checksum esperado na página HTTPS confiável do publicador ou em um manifesto assinado cuja assinatura você tenha verificado com uma chave confiável. Se alguém puder substituir tanto o arquivo quanto o checksum esperado, essa pessoa poderá fazer a comparação passar. O guia de verificação de releases da Apache explica a diferença entre conferir um hash e autenticar uma release.
Crie um verificador de arquivos SHA256
Você precisa ter o Go instalado e um shell. Estes comandos foram testados com o Go 1.27.1 no Linux usando Bash. O programa usa apenas a biblioteca padrão, então não há pacotes para baixar e nenhuma configuração de módulo é necessária para este build de arquivo único.
Crie um novo diretório de trabalho. Continue somente se este comando for bem-sucedido; ele se recusa a reutilizar um diretório existente:
mkdir sha256-example && cd sha256-example
Salve o seguinte conteúdo como main.go dentro desse diretório:
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
)
func hashFile(path string) (sum []byte, err error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer func() {
if closeErr := file.Close(); closeErr != nil && err == nil {
sum, err = nil, closeErr
}
}()
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return nil, err
}
return hash.Sum(nil), nil
}
func run(args []string) int {
if len(args) < 1 || len(args) > 2 {
fmt.Fprintln(os.Stderr, "Usage: sha256file FILE [EXPECTED_SHA256]")
return 2
}
var expected []byte
if len(args) == 2 {
var err error
expected, err = hex.DecodeString(args[1])
if err != nil || len(expected) != sha256.Size {
fmt.Fprintln(os.Stderr, "Expected SHA256 must be exactly 64 hexadecimal characters")
return 2
}
}
actual, err := hashFile(args[0])
if err != nil {
fmt.Fprintln(os.Stderr, "sha256file:", err)
return 2
}
if len(args) == 1 {
fmt.Printf("%x\n", actual)
return 0
}
if !bytes.Equal(actual, expected) {
fmt.Fprintf(os.Stderr, "FAIL: %q (SHA256 mismatch)\n", args[0])
return 1
}
fmt.Printf("OK: %q\n", args[0])
return 0
}
func main() {
os.Exit(run(os.Args[1:]))
}
os.Open abre a entrada para leitura. io.Copy envia os bytes do arquivo
ao hash até o EOF ou um erro, e hash.Sum(nil) retorna o
digest desses bytes. O fechamento adiado é executado antes de hashFile retornar, inclusive em erros
de leitura. Somente depois que run retorna é que main chama os.Exit, que, caso
contrário, ignoraria as chamadas adiadas pendentes.
O valor esperado é decodificado e tem seu comprimento obrigatório verificado antes da leitura do
arquivo. A decodificação também faz com que checksums hexadecimais em maiúsculas e em minúsculas
sejam comparados como iguais. Esta CLI compara checksums públicos com bytes.Equal; ela não tem nenhum
valor secreto de comparação a proteger. Para autenticação de mensagens com chave, use um design HMAC
separado e hmac.Equal.
Compile o executável no mesmo diretório. Recompilar substitui um executável Go existente chamado
sha256file; escolha outro nome de saída se esse caminho já estiver sendo usado por um arquivo não
relacionado:
go build -o sha256file main.go
Verifique um arquivo e inspecione o status de saída
Crie uma pequena entrada contendo exatamente os três bytes abc, sem quebra de linha final, e
calcule o hash dela. Este comando cria ou sobrescreve o arquivo descartável example.txt no seu diretório
de trabalho:
printf 'abc' > example.txt && ./sha256file example.txt
A saída é:
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
Agora verifique-o com esse checksum conhecido:
./sha256file example.txt ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
O programa termina com status 0 e imprime:
OK: "example.txt"
Esta entrada conhecida demonstra o funcionamento. Para um download real, passe o caminho dele e o
checksum confiável do publicador; calcular um novo checksum a partir do arquivo baixado e compará-lo
com ele mesmo não comprova a integridade. Coloque entre aspas os caminhos que contêm espaços. O
segundo argumento aceita apenas o digest de 64 caracteres, sem nome de arquivo, prefixo sha256: nem
espaços em branco ao redor.
Substitua o conteúdo da amostra e confira-a com o checksum original. Em um shell Bash interativo
sem set -e, execute estas linhas juntas para que $? capture imediatamente o status do verificador:
printf 'changed\n' > example.txt &&
./sha256file example.txt ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
printf 'Exit status: %s\n' "$?"
Você deverá ver:
FAIL: "example.txt" (SHA256 mismatch)
Exit status: 1
O verificador envia as falhas para a saída de erro padrão e os resultados bem-sucedidos para a saída padrão. Seus códigos de saída permitem que um script decida se deve continuar:
| Status | Significado |
|---|---|
0 | Hash impresso, ou o arquivo corresponde ao checksum fornecido |
1 | O arquivo foi lido com sucesso, mas seu checksum é diferente |
2 | Argumentos inválidos, checksum malformado ou erro ao abrir, ler ou fechar o arquivo |
Use o executável compilado ao conferir esses códigos; go run informa a falha de um programa filho
por meio da ferramenta Go e não preserva o status de saída exato dele. O verificador abre entradas
existentes sem modificá-las e informa arquivos ausentes em vez de criar substitutos.
Processe arquivos grandes em streaming e mantenha a entrada estável
O mesmo programa lida com um arquivo vazio, dados binários ou um pacote de arquivos grande. Ele
processa os bytes de forma incremental em vez de alocar um slice do tamanho do arquivo. Cada byte
ainda precisa ser lido, então a velocidade do armazenamento e a taxa de hashing afetam o tempo
decorrido. Não existe um tamanho de buffer universal que torne esta tarefa a mais rápida. Meça sua
carga de trabalho real antes de substituir io.Copy por um buffering personalizado e leve em conta o
cache do sistema de arquivos ao repetir uma medição.
Termine de baixar ou gravar o arquivo antes de calcular o hash dele e mantenha-o inalterado até que o consumidor o utilize. Este programa não bloqueia o arquivo nem cria um snapshot. Uma comparação bem-sucedida verifica os bytes que foram lidos; ela não pode impedir que outro processo os altere depois.
Armadilhas comuns
Uma divergência pode indicar corrupção, mas também pode significar que você usou um checksum de outra release, plataforma ou pacote de arquivos. Calcule o hash exatamente do arquivo que o publicador indica: um pacote de arquivos e o conteúdo extraído dele têm digests diferentes. Adicionar uma quebra de linha ou converter os finais de linha também altera os bytes.
Um arquivo vazio tem um digest SHA256 válido e não é uma falha de leitura. Ainda assim, ele não passará na verificação contra o checksum de um download não vazio. Se a leitura falhar no meio do caminho, o programa informa um erro em vez de comparar um digest parcial. Para uma entrada ausente ou inacessível, confira o caminho e as permissões antes de tentar novamente; um erro de E/S não determina se o arquivo corresponde.
