Dateiintegrität mit Go und SHA256 prüfen
Um eine Datei in Go zu prüfen, streamen Sie ihre Bytes in sha256.New() mit io.Copy
und vergleichen Sie den Digest mit einer Prüfsumme aus einer vertrauenswürdigen Quelle. Das folgende
Kommandozeilenprogramm kann den Hash einer Datei ausgeben oder einen erwarteten Hash prüfen.
Schlägt die Prüfung fehl, gibt es einen Exit-Status ungleich null zurück.
Mit einer vertrauenswürdigen Prüfsumme beginnen
SHA256 erzeugt einen 256-Bit-Digest: 32 Bytes, meist als 64 Hexadezimalzeichen dargestellt.
Die Go-Standardbibliothek stellt ihn über crypto/sha256 bereit.
Ein Digest beschreibt die Bytes der Datei; er ist weder eine digitale Signatur noch eine Garantie,
dass sich die Datei sicher ausführen lässt.
Beziehen Sie die erwartete Prüfsumme für einen Download von der vertrauenswürdigen HTTPS-Seite des Herausgebers oder aus einem signierten Manifest, dessen Signatur Sie mit einem vertrauenswürdigen Schlüssel geprüft haben. Wer sowohl die Datei als auch die erwartete Prüfsumme ersetzen kann, kann den Vergleich erfolgreich ausfallen lassen. Die Anleitung zur Release-Verifizierung von Apache erläutert den Unterschied zwischen der Prüfung eines Hashs und der Authentifizierung eines Releases.
Einen SHA256-Dateiprüfer erstellen
Sie benötigen eine Go-Installation und eine Shell. Diese Befehle wurden mit Go 1.27.1 unter Linux mit Bash getestet. Das Programm nutzt nur die Standardbibliothek. Für diesen Build aus einer einzelnen Datei müssen Sie daher weder Pakete herunterladen noch ein Modul einrichten.
Erstellen Sie ein neues Arbeitsverzeichnis. Fahren Sie nur fort, wenn dieser Befehl erfolgreich ist; er verweigert die Wiederverwendung eines vorhandenen Verzeichnisses:
mkdir sha256-example && cd sha256-example
Speichern Sie Folgendes als main.go in diesem Verzeichnis:
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 öffnet die Eingabe zum Lesen. io.Copy
führt dem Hash die Bytes der Datei bis zum EOF oder einem Fehler zu, und
hash.Sum(nil) gibt den Digest dieser Bytes zurück.
Das verzögerte Schließen erfolgt, bevor hashFile zurückkehrt, auch bei Lesefehlern.
Erst nach der Rückkehr von run ruft main
os.Exit auf. Dieser Aufruf würde sonst noch ausstehende
verzögerte Aufrufe überspringen.
Der erwartete Wert wird vor dem Lesen der Datei decodiert und auf die erforderliche Länge geprüft.
Durch das Decodieren werden hexadezimale Prüfsummen mit Groß- und Kleinbuchstaben als gleich erkannt.
Dieses CLI vergleicht öffentliche Prüfsummen mit bytes.Equal; es gibt keinen geheimen
Vergleichswert zu schützen. Nutzen Sie zur Nachrichtenauthentifizierung mit Schlüssel einen separaten
HMAC-Ansatz und hmac.Equal.
Erstellen Sie die ausführbare Datei im selben Verzeichnis. Ein erneuter Build ersetzt eine vorhandene
ausführbare Go-Datei namens sha256file; wählen Sie einen anderen Ausgabenamen, wenn
dieser Pfad bereits für eine andere Datei verwendet wird:
go build -o sha256file main.go
Eine Datei prüfen und den Exit-Status auswerten
Erstellen Sie eine kleine Eingabe mit genau den drei Bytes abc ohne
abschließenden Zeilenumbruch und berechnen Sie ihren Hash. Dieser Befehl erstellt oder überschreibt
die entbehrliche Testdatei example.txt in Ihrem Arbeitsverzeichnis:
printf 'abc' > example.txt && ./sha256file example.txt
Die Ausgabe lautet:
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
Prüfen Sie die Datei nun anhand dieser bekannten Prüfsumme:
./sha256file example.txt ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
Das Programm beendet sich mit dem Status 0 und gibt Folgendes aus:
OK: "example.txt"
Diese bekannte Eingabe veranschaulicht die Funktionsweise. Für einen tatsächlichen Download übergeben
Sie dessen Pfad und die vertrauenswürdige Prüfsumme des Herausgebers. Eine neue Prüfsumme aus der
heruntergeladenen Datei zu berechnen und mit sich selbst zu vergleichen, kann keine Integrität
nachweisen. Setzen Sie Pfade mit Leerzeichen in Anführungszeichen. Das zweite Argument akzeptiert nur
den 64 Zeichen langen Digest, ohne Dateinamen, Präfix sha256: oder umgebende
Leerraumzeichen.
Ersetzen Sie den Inhalt der Beispieldatei und prüfen Sie diese anhand der ursprünglichen Prüfsumme.
Führen Sie diese Zeilen in einer interaktiven Bash-Shell ohne set -e zusammen
aus, damit $? den Status des Prüfprogramms sofort erfasst:
printf 'changed\n' > example.txt &&
./sha256file example.txt ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
printf 'Exit status: %s\n' "$?"
Sie sollten Folgendes sehen:
FAIL: "example.txt" (SHA256 mismatch)
Exit status: 1
Das Prüfprogramm sendet Fehler an die Standardfehlerausgabe und erfolgreiche Ergebnisse an die Standardausgabe. Anhand seiner Exit-Codes kann ein Skript entscheiden, ob es fortfahren soll:
| Status | Bedeutung |
|---|---|
0 | Hash ausgegeben oder Datei stimmt mit der übergebenen Prüfsumme überein |
1 | Datei erfolgreich gelesen, aber ihre Prüfsumme weicht ab |
2 | Ungültige Argumente, fehlerhaft formatierte Prüfsumme oder Fehler beim Öffnen, Lesen oder Schließen der Datei |
Führen Sie die erstellte ausführbare Datei aus, wenn Sie diese Codes prüfen;
go run meldet das Scheitern eines untergeordneten Programms über das Go-Tool
und bewahrt dessen genauen Exit-Status nicht. Das Prüfprogramm öffnet vorhandene Eingaben, ohne sie
zu verändern, und meldet fehlende Dateien, statt Ersatzdateien zu erstellen.
Große Dateien streamen und die Eingabe unverändert halten
Dasselbe Programm verarbeitet eine leere Datei, Binärdaten oder ein großes Archiv. Es verarbeitet
Bytes schrittweise, statt einen Slice von der Größe der Datei zu reservieren. Jedes Byte muss dennoch
gelesen werden. Daher beeinflussen die Geschwindigkeit des Speichers und der Hash-Durchsatz die
benötigte Zeit. Es gibt keine universelle Puffergröße, mit der diese Aufgabe am schnellsten ausgeführt
wird. Messen Sie Ihre tatsächliche Arbeitslast, bevor Sie io.Copy durch eine
eigene Pufferung ersetzen, und berücksichtigen Sie bei wiederholten Messungen den Dateisystem-Cache.
Schließen Sie den Download oder Schreibvorgang der Datei ab, bevor Sie ihren Hash berechnen, und halten Sie die Datei unverändert, bis die verarbeitende Anwendung sie verwendet. Dieses Programm sperrt die Datei nicht und erstellt keinen Snapshot. Ein erfolgreicher Vergleich prüft die gelesenen Bytes; er kann nicht verhindern, dass ein anderer Prozess sie danach verändert.
Häufige Fallstricke
Eine Abweichung kann auf beschädigte Daten hinweisen, aber auch darauf, dass Sie eine Prüfsumme für ein anderes Release, eine andere Plattform oder ein anderes Archiv verwendet haben. Berechnen Sie den Hash genau der Datei, die der Herausgeber nennt: Ein Archiv und seine extrahierten Inhalte haben unterschiedliche Digests. Auch das Hinzufügen eines Zeilenumbruchs oder das Konvertieren von Zeilenenden verändert die Bytes.
Eine leere Datei hat einen gültigen SHA256-Digest und stellt keinen Lesefehler dar. Ihre Prüfung anhand der Prüfsumme eines nicht leeren Downloads schlägt dennoch fehl. Scheitert das Lesen unterwegs, meldet das Programm einen Fehler, statt einen unvollständigen Digest zu vergleichen. Prüfen Sie bei einer fehlenden oder nicht zugänglichen Eingabe den Pfad und die Berechtigungen, bevor Sie es erneut versuchen; ein E/A-Fehler sagt nichts darüber aus, ob die Datei übereinstimmt.
