Verifica la integridad de archivos con Go y SHA256
Para verificar un archivo en Go, transmite sus bytes a sha256.New() con io.Copy y compara el resumen
con una suma de verificación obtenida de una fuente de confianza. El programa de línea de comandos
que se muestra a continuación puede imprimir el hash de un archivo o verificar uno esperado, y
termina con un estado de salida distinto de cero si la verificación falla.
Empieza con una suma de verificación de confianza
SHA256 produce un resumen de 256 bits: 32 bytes, que suelen escribirse como 64 caracteres
hexadecimales. La biblioteca estándar de Go lo proporciona mediante
crypto/sha256.
Un resumen describe los bytes del archivo; no es una firma digital ni una garantía de que sea
seguro ejecutarlo.
Para una descarga, obtén la suma de verificación esperada de la página HTTPS de confianza del publicador o de un manifiesto firmado cuya firma hayas verificado con una clave de confianza. Si alguien puede reemplazar tanto el archivo como la suma de verificación esperada, puede hacer que la comparación sea satisfactoria. La guía de verificación de versiones de Apache explica la diferencia entre comprobar un hash y autenticar una versión.
Crea un verificador de archivos SHA256
Necesitas tener Go instalado y un intérprete de comandos. Estos comandos se probaron con Go 1.27.1 en Linux usando Bash. El programa usa solo la biblioteca estándar, por lo que no hay paquetes que descargar ni es necesario configurar un módulo para compilar este único archivo.
Crea un nuevo directorio de trabajo. Continúa solo si este comando se ejecuta correctamente; el comando no permite reutilizar un directorio existente:
mkdir sha256-example && cd sha256-example
Guarda lo siguiente como main.go dentro de ese directorio:
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 la entrada para su lectura.
io.Copy suministra los bytes del archivo
al hash hasta llegar a EOF o a un error, y
hash.Sum(nil) devuelve el resumen de esos bytes.
El cierre diferido se ejecuta antes de que hashFile retorne, incluso si hay
errores de lectura.
Solo después de que run retorna, main llama a
os.Exit, que de otro modo omitiría las
llamadas diferidas pendientes.
El valor esperado se decodifica y se comprueba que tenga la longitud requerida antes de leer el
archivo. La decodificación también permite comparar como iguales las sumas de verificación
hexadecimales en mayúsculas y minúsculas. Esta CLI compara sumas de verificación públicas con
bytes.Equal; no tiene ningún valor de comparación secreto que proteger. Para la
autenticación de mensajes con clave, usa un diseño HMAC independiente y
hmac.Equal.
Compila el ejecutable en el mismo directorio. Volver a compilar reemplaza cualquier ejecutable de Go
existente llamado sha256file; elige otro nombre de salida si esa ruta ya se usa
para un archivo no relacionado:
go build -o sha256file main.go
Verifica un archivo e inspecciona el estado de salida
Crea una entrada pequeña que contenga exactamente los tres bytes abc,
sin un salto de línea final, y calcula su hash. Este comando crea o sobrescribe el archivo
descartable example.txt en tu directorio de trabajo:
printf 'abc' > example.txt && ./sha256file example.txt
La salida es:
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
Ahora verifícalo con esa suma de verificación conocida:
./sha256file example.txt ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
El programa termina con el estado 0 e imprime:
OK: "example.txt"
Esta entrada conocida demuestra el funcionamiento. Para una descarga real, pasa su ruta y la suma
de verificación de confianza del publicador; calcular una nueva suma a partir del archivo
descargado y compararla consigo misma no permite establecer su integridad. Pon entre comillas las
rutas que contengan espacios. El segundo argumento solo acepta el resumen de 64 caracteres, sin
nombre de archivo, prefijo sha256: ni espacios en blanco alrededor.
Reemplaza el contenido del archivo de ejemplo y compruébalo con la suma de verificación original.
En una sesión interactiva de Bash sin set -e, ejecuta estas líneas juntas
para que $? capture de inmediato el estado del verificador:
printf 'changed\n' > example.txt &&
./sha256file example.txt ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
printf 'Exit status: %s\n' "$?"
Deberías ver lo siguiente:
FAIL: "example.txt" (SHA256 mismatch)
Exit status: 1
El verificador envía los fallos a la salida de error estándar y los resultados satisfactorios a la salida estándar. Sus códigos de salida permiten que un script decida si debe continuar:
| Estado | Significado |
|---|---|
0 | Se imprimió el hash o el archivo coincide con la suma de verificación proporcionada |
1 | El archivo se leyó correctamente, pero su suma de verificación difiere |
2 | Argumentos no válidos, suma de verificación mal formada o error al abrir, leer o cerrar un archivo |
Ejecuta el programa compilado al comprobar estos códigos; go run informa
del fallo de un programa hijo a través de la herramienta de Go y no conserva su estado de salida
exacto. El verificador abre las entradas existentes sin modificarlas e informa de los archivos
faltantes en lugar de crear reemplazos.
Procesa archivos grandes en flujo y mantén la entrada estable
El mismo programa admite un archivo vacío, datos binarios o un archivo contenedor grande. Procesa
los bytes de forma incremental en lugar de asignar un slice del tamaño del archivo. Aun así, debe
leer cada byte, por lo que la velocidad del almacenamiento y el rendimiento del cálculo de hashes
afectan al tiempo transcurrido. No hay un tamaño de búfer universal que permita realizar esta tarea
con la máxima rapidez. Mide tu carga de trabajo real antes de reemplazar
io.Copy por una gestión de búferes personalizada, y ten en cuenta la caché
del sistema de archivos al repetir una medición.
Termina de descargar o escribir el archivo antes de calcular su hash y mantenlo sin cambios hasta que lo use el consumidor. Este programa no bloquea el archivo ni crea una instantánea. Una comparación satisfactoria comprueba los bytes que se leyeron; no puede impedir que otro proceso los cambie después.
Problemas comunes
Una discrepancia puede indicar que los datos están dañados, pero también que usaste una suma de verificación de otra versión, plataforma o archivo contenedor. Calcula el hash del archivo exacto que indique el publicador: un archivo contenedor y su contenido extraído tienen resúmenes diferentes. Añadir un salto de línea o convertir los finales de línea también cambia los bytes.
Un archivo vacío tiene un resumen SHA256 válido y no supone un fallo de lectura. Aun así, no superará la verificación con la suma de una descarga no vacía. Si la lectura falla antes de completarse, el programa informa de un error en lugar de comparar un resumen parcial. Si una entrada no existe o no es accesible, comprueba la ruta y los permisos antes de volver a intentarlo; un error de E/S no permite establecer si el archivo coincide.
