Was beweisen reproduzierbare Builds wirklich?
Eine Prüfsumme zeigt nur eine unbeschädigte Datei. Reproduzierbare Builds belegen, dass sie exakt aus dem überprüfbaren Quellcode entstanden ist.
Was reproduzierbare Builds beweisen
Reproduzierbare Builds beweisen eine eng umrissene Tatsache: Die Binärdatei, die Sie erhalten haben, ist die Binärdatei, die genau dieser Quellcode erzeugt. Jeder kann denselben Quellcode erneut bauen und die Bytes vergleichen. Die Verifizierung ist damit nicht mehr nur durch den Herausgeber möglich.
Das Projekt Reproducible Builds definiert dies folgendermaßen: „Ein Build ist reproduzierbar, wenn jede Partei ausgehend von demselben Quellcode, derselben Build-Umgebung und denselben Build-Anweisungen bitweise identische Kopien aller angegebenen Artefakte erstellen kann.“ Der Vergleich selbst erfolgt über einen Hash. Die eigentliche Schwierigkeit besteht darin, die Umgebung und die Anweisungen so genau festzulegen, dass zwei verschiedene Rechner zum selben Ergebnis kommen.
Warum eine Prüfsumme diese Frage nicht beantwortet
Eine veröffentlichte Prüfsumme bestätigt, dass die heruntergeladene Datei mit dem Referenzwert übereinstimmt und somit ohne Beschädigung übertragen wurde. Sie sagt nichts darüber aus, wer die Datei erstellt hat. Apaches Anleitung zur Verifizierung formuliert es bei Hashes eindeutig: „Sie geben keinerlei Garantie für die Authentizität der Datei.“ Die Referenzprüfsumme muss aus einer Quelle stammen, der Sie vertrauen können. Andernfalls beweist eine Übereinstimmung nichts. Eine Signatur über dieser Prüfsumme beweist, dass sie von der Person stammt, die den Schlüssel besitzt, sobald Sie geprüft haben, dass der Schlüssel tatsächlich dem Herausgeber gehört. Keine der beiden Prüfungen sagt etwas darüber aus, was geschah, bevor das Artefakt entstand. Wenn das Build-System des Herausgebers kompromittiert ist, wird die schädliche Binärdatei genau wie eine unveränderte Datei mit einer Prüfsumme versehen und signiert. Dadurch besteht jede nachgelagerte Prüfung. Dasselbe gilt, wenn ein Maintainer aus einem Arbeitsverzeichnis baut, das nie in das Repository übertragen wurde.
Das ist die Lücke. Sie können den Quellcode lesen, die Signatur verifizieren und die Prüfsumme verifizieren und trotzdem Code ausführen, der nie im Repository enthalten war. Reproduzierbarkeit ist daher ein anderes Thema als ein Download anhand seiner veröffentlichten Prüfsumme zu verifizieren. Die Prüfsumme schützt die Übertragung. Der erneute Build schützt alles, was vor der Übertragung geschah.
Der Angriff ist nicht nur theoretisch. Die Kompromittierung von SolarWinds Orion im Jahr 2020 befand sich genau an dieser Stelle: Das Build-System erzeugte signierte Artefakte, die nicht dem Quellcode entsprachen, den irgendjemand geprüft hatte. Jede Signaturprüfung war erfolgreich, weil Signaturen erst beim Artefakt ansetzen.
Was ein reproduzierbarer Build nicht beweist
Dieser Aspekt wird oft überbewertet. Beachten Sie deshalb genau die Grenzen.
- Er sagt nicht, dass der Quellcode sicher ist. Eine offen eingebrachte Hintertür wird reproduzierbar gebaut, und jeder erneute Builder bestätigt sie, weil alle denselben schädlichen Quellcode gebaut haben. Reproduzierbarkeit verlagert den Prüfpunkt in den Quellcode. Dieser Quellcode muss weiterhin von jemandem geprüft werden. Deshalb bleibt auch eine Review-Richtlinie, einschließlich Richtlinien für KI-gestützten Code in Open-Source-Projekten, eine separate Kontrollmaßnahme.
- Sie sagt nicht, dass Ihre Eingaben sicher sind. Abhängigkeiten sind Teil dessen, was Sie bauen. Ein beim Build aufgelöstes schädliches Paket wird in das Artefakt kompiliert. Jeder erneute Builder, der dieselbe Abhängigkeit auflöst, erhält dasselbe Ergebnis wie Sie. So erreicht ein npm-Supply-Chain-Angriff einen Server, und ein reproduzierbarer Build reproduziert ihn zuverlässig.
- Sie sagt nicht, dass die Toolchain vertrauenswürdig ist. Wenn der Compiler kompromittiert wurde, erzeugt jeder erneute Builder mit diesem Compiler dieselbe kompromittierte Ausgabe, und alle Prüfergebnisse stimmen überein. Reproduzierbarkeit erhöht den Aufwand für diesen Angriff. Sie erkennt ihn jedoch nicht.
- Sie sagt nichts über Sicherheitslücken aus. Eine bitgenau reproduzierte alte Bibliothek bleibt eine alte Bibliothek mit ihren veröffentlichten Schwachstellen. Führen Sie deshalb weiterhin eigene Prüfungen Ihres Servers auf bekannte CVEs nach einem separaten Zeitplan durch.
Durch Reproduzierbarkeit entfällt eine bestimmte Position des Angreifers: die Build-Maschine und der gesamte Weg vom Quellcode zum Binärprogramm. Solange ein Paket nicht reproduzierbar ist, kann niemand außerhalb des Herausgebers diesen Weg überhaupt prüfen.
Warum dieselbe Quelle unterschiedliche Bytes erzeugt
Die meisten Programme sind standardmäßig nicht reproduzierbar. Die Ursachen sind meist unspektakulär. Compiler und Archivformate speichern Informationen über das System, auf dem sie ausgeführt wurden.
- Ein Zeitstempel. Die Formate
tar,arundzipspeichern die Änderungszeiten von Dateien. Ein Build zu einer anderen Sekunde verändert daher die Ausgabedatei. - Ein Pfad. Debug-Informationen speichern das absolute Build-Verzeichnis. Ein Build in
/home/alice/srcund ein Build in/build/pkgunterscheiden sich daher, obwohl der Code identisch ist. - Eine Reihenfolge. Beim Lesen eines Verzeichnisses werden die Einträge in der Reihenfolge des Dateisystems zurückgegeben. Dadurch ändert sich zwischen Systemen die Link-Zeile oder die Reihenfolge der Archivmitglieder.
- Eine Identität. Build-Skripte betten den Benutzernamen, den Hostnamen oder das Gebietsschema des Benutzers ein, der den Build ausgeführt hat.
- Eine Entscheidung zur Build-Zeit. Die Erkennung von CPU-Features oder das zufällige Initialisieren eines Werts macht die Ausgabe vom System statt von der Quelle abhängig.
Die erste Ursache lässt sich in etwa zehn Sekunden beobachten:
mkdir -p /tmp/rb && cd /tmp/rb
echo hello > a.txt && tar cf one.tar a.txt
sleep 2
echo hello > a.txt && tar cf two.tar a.txt
sha256sum one.tar two.tarDie beiden Hashes unterscheiden sich, weil der tar-Header die Änderungszeit von a.txt speichert. Durch das erneute Schreiben der Datei wurde diese Zeit um zwei Sekunden vorgestellt. Der Inhalt ist Byte für Byte identisch. Durch das Fixieren der Metadaten lässt sich das Problem beheben:
export SOURCE_DATE_EPOCH=1700000000
tar --sort=name --mtime="@${SOURCE_DATE_EPOCH}" --owner=0 --group=0 --numeric-owner -cf three.tar a.txt
touch a.txt
tar --sort=name --mtime="@${SOURCE_DATE_EPOCH}" --owner=0 --group=0 --numeric-owner -cf four.tar a.txt
sha256sum three.tar four.tarJetzt stimmen die Hashes überein, weil kein Feld im Archiv-Header aus dem aktuellen Zustand des Systems stammt. --sort=name legt die Reihenfolge fest, --mtime legt die Uhrzeit fest, und die Eigentümer-Flags verhindern, dass die Benutzer-ID Ihres Kontos gespeichert wird.
Der Unterschied mit diffoscope
Wenn sich zwei Builds unterscheiden, zeigt sha256sum nur, dass sie sich unterscheiden, und nichts weiter. diffoscope zeigt Ihnen, warum das so ist, und stellt die Unterschiede in einer für Menschen lesbaren Form dar. Das Tool entpackt beide Seiten rekursiv, wandelt Binärformate in Text um und vergleicht den Text. Es verarbeitet Debian-Pakete, ELF-Binärdateien, tar- und ZIP-Archive, PDFs, SQLite-Datenbanken und Dutzende weitere Formate.
sudo apt install -y diffoscope
diffoscope one.tar two.tarFür das oben genannte tar-Paar ist der Bericht kurz. Gekürzt sieht er so aus:
--- one.tar
+++ two.tar
├── file list
│ @@ -1 +1 @@
│ -rw-r--r-- 0/0 6 2026-08-18 10:14:02.000000 a.txt
│ +rw-r--r-- 0/0 6 2026-08-18 10:14:04.000000 a.txtDas ist die vollständige Diagnose: gleiche Größe, gleicher Pfad, gleiche Berechtigungen, unterschiedliche Änderungszeit. Ein echtes Paket erzeugt einen deutlich längeren Bericht. Schreiben Sie ihn deshalb in eine Datei und öffnen Sie ihn in einem Browser:
diffoscope --html report.html build1.changes build2.changesdiffoscope beendet sich mit 0, wenn die Eingaben identisch sind, mit 1, wenn sie sich unterscheiden, und mit 2, wenn ein Fehler auftritt. Es lässt sich daher ohne Wrapper-Skript direkt in einen CI-Job integrieren. Installieren Sie auf einem kleinen VPS diffoscope-minimal statt diffoscope. Das vollständige Paket installiert eine große Anzahl an Format-Hilfsprogrammen, die Sie wahrscheinlich nie aufrufen werden.
Was SOURCE_DATE_EPOCH behebt und wo die Grenzen liegen
SOURCE_DATE_EPOCH ist eine Umgebungsvariable, die eine Zahl enthält: den Zeitpunkt der letzten Änderung des Quellcodes, angegeben als Sekunden seit dem 1. Januar 1970 UTC. Ein Build-Tool, das die Variable unterstützt, verwendet diese Zahl überall dort, wo es sonst die aktuelle Zeit vom Betriebssystem abfragen würde. Setzen Sie sie aus der Versionsverwaltung, damit der Wert dem Quellcode folgt und nicht dem Build:
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)In einem Debian-Paket exportiert debhelper die Variable für Sie aus dem Changelog. So setzen Sie sie manuell in debian/rules:
export SOURCE_DATE_EPOCH ?= $(shell dpkg-parsechangelog -STimestamp)Die Unterstützung gilt pro Tool und niemals global. cmake 3.8 und höher, gcc 7 und höher, rpm oberhalb von 4.13 und Docker buildx 0.10 und höher lesen sie aus. Ihre eigenen Skripte tun das nicht, sofern Sie sie nicht entsprechend programmieren. Wenn ein Skript date aufruft, übergeben Sie ihm die Variable:
BUILD_DATE="$(date --utc --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y-%m-%d)"Bei der Implementierung ist eine Regel wichtig. Wenn die Variable bereits gesetzt ist, gilt ihr Wert für Ihren Build als aktuelle Zeit. Verwenden Sie ihn daher überall dort, wo Sie sonst die Uhrzeit gelesen hätten, und lassen Sie die Variable für jeden Prozess gesetzt, den Ihr Build startet. Die Spezifikation formuliert es eindeutig: "Build processes MUST NOT unset this variable for child processes if it is already present."
Bei Container-Images besteht dasselbe Problem in einer anderen Verpackung. Docker buildx 0.10 und höher übergibt SOURCE_DATE_EPOCH aus Ihrer Shell als Build-Argument an den Build. Die Zeitstempel der Dateien in den Layern müssen vom Exporter neu geschrieben werden. Diese Funktion wurde in BuildKit 0.13 hinzugefügt. Die dokumentierte Form überträgt das Ergebnis an eine Registry:
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
docker buildx build --output type=image,name=registry.example.com/app:1.0,push=true,rewrite-timestamp=true .Das eigene Build mit reprotest testen
reprotest erstellt zweimal ein Build aus demselben Quelltext, verändert die Umgebung zwischen den beiden Builds absichtlich und vergleicht anschließend die Ergebnisse. Genau diese Variationen sind der Zweck des Tests. Standardmäßig variiert das Programm die Umgebungsvariablen, den Build-Pfad, die Kernelversion, die Randomisierung des Adressraums, die Anzahl der CPUs, die Uhrzeit, den Benutzer und die Gruppe, die Dateireihenfolge, die Domain und den Hostnamen, das Home-Verzeichnis, das Locale, den Pfad zu ausführbaren Dateien, die Zeitzone und die umask.
sudo apt install -y reprotest
reprotest . -- nullAlles nach -- wählt das Backend für die Build-Umgebung aus, und null bezeichnet das System, auf dem Sie sich gerade befinden. Fügen Sie -vv -d hinzu, um die temporären Verzeichnisse zur Überprüfung beizubehalten, wie in reprotest . -vv -- null -d. Verwenden Sie reprotest auto -- null, damit reprotest selbst ermittelt, um welche Art von Quelltextbaum es sich handelt.
Für einige Variationen sind Berechtigungen oder zusätzliche Pakete erforderlich. Wenn sie nicht ausgeführt werden können, brechen sie mit einer eindeutigen Fehlermeldung ab. Deaktivieren Sie diese Variationen, statt den gesamten Vorgang als root auszuführen:
reprotest --vary=-user_group,-domain_host,-fileordering auto -- nullAlles, was reprotest meldet, hätte ein Rebuilder später öffentlich gemeldet, zusammen mit dem Namen Ihres Projekts.
Bedeutung eines Rebuilder-Ergebnisses
Ein Rebuilder ist eine Maschine, die nicht dem Publisher gehört. Sie nimmt den veröffentlichten Quellcode und die aufgezeichnete Build-Umgebung, erstellt das Paket erneut und vergleicht die eigene Ausgabe mit dem Artefakt im Archiv. Das Ergebnis ist nur deshalb aussagekräftig, weil die Maschine unabhängig ist.
Debian erfasst die Umgebung in einer .buildinfo-Datei, die dpkg-buildpackage neben der .deb ablegt. Entscheidend sind die Felder. Installed-Build-Depends listet jedes installierte Paket auf, das den Build beeinflussen könnte, einschließlich der exakten Versionen. Build-Path zeichnet auf, wo der Build ausgeführt wurde. Environment erfasst die Umgebungsvariablen, die bekanntermaßen relevant sind. Checksums-Sha256 beschreibt die Ausgaben. Diese Datei ist die Anleitung für einen zweiten Versuch:
sudo apt install -y devscripts mmdebstrap
debrebuild --buildresult=./artifacts --builder=mmdebstrap hello_2.10-2_amd64.buildinfodebrebuild liest die Buildinfo und lädt die darin angegebenen exakten Abhängigkeitsversionen von snapshot.debian.org. Dadurch kann ein Rebuild heute die Paketversionen verwenden, die am Tag des ursprünglichen Builds vorhanden waren. Der mmdebstrap-Builder benötigt weder eine chroot-Einrichtung noch Superuser-Rechte. Vergleichen Sie die erzeugten Artefakte mit der Kopie im Archiv mit diffoscope.
Arch Linux führt rebuilderd aus. Der Dienst wiederholt diesen Vorgang kontinuierlich und veröffentlicht die Ergebnisse:
rebuildctl -H https://reproducible.archlinux.org pkgs ls --name rebuilderdDie Statuswerte sind GOOD, BAD und UNKWN. Die einfache Interpretation jedes Status ist jedoch aus einem anderen Grund falsch. GOOD bedeutet, dass eine unabhängige Stelle dieselben Bytes erzeugt hat. Das ist eine starke Aussage über den Build, aber überhaupt keine Aussage über den Quellcode. BAD ist fast nie ein Angriff. Die übliche Ursache ist ein Zeitstempel oder ein Pfad, den die Paketierung nicht festgeschrieben hat. Deshalb kann rebuilderd dem Fehler einen diffoscope-Bericht hinzufügen. UNKWN bedeutet, dass niemand das Paket getestet hat. Ein ungetestetes Paket hat daher nicht den Status bestanden.
Die operative Regel ist kurz. Ein BAD-Ergebnis ist ein Grund, den Bericht zu lesen. Zeigt der Bericht Zeitstempel, Build-Pfade oder eine andere Reihenfolge von Mitgliedern, melden Sie einen Paketierungsfehler. Zeigt er abweichenden ausführbaren Code ohne eine solche Erklärung, beenden Sie die Bereitstellung dieses Builds und eskalieren Sie den Vorgang.
Wie reproduzierbar ist Debian derzeit?
Die Daten hinter diesem Diagramm
[
{
"label": "unstable",
"percent_reproducible": 94.8,
"tested_count": "41,490"
},
{
"label": "forky",
"percent_reproducible": 95.7,
"tested_count": "39,529"
},
{
"label": "experimental",
"percent_reproducible": 72.5,
"tested_count": "619"
}
]Am 2026-10-15 war unstable auf amd64 bei 94.8% reproduzierbaren Builds, bezogen auf 41,490 getestete Pakete. Experimental lag bei 72.5% und damit bei einer deutlich kleineren und deutlich neueren Stichprobe von 619 Paketen. Das entspricht den Erwartungen für Pakete, deren Fehlerbehebung noch nicht abgeschlossen ist.
Diese Zahlen stammen von der Debian-Seite auf tests.reproducible-builds.org, abgerufen am 2026-10-15. Zu diesem Zeitpunkt trug die Seite den Zeitstempel "Last update: 2026-10-15 13:02 UTC". Die Werte ändern sich. Lesen Sie in sechs Monaten den Tracker, statt diesen Absatz zu zitieren.
Ein Vorbehalt ist wichtiger als der Prozentsatz. Dieses Framework baut jedes Paket zweimal auf eigener Hardware. Zwischen den beiden Builds wird die Umgebung verändert, anschließend werden die beiden eigenen Ergebnisse verglichen. Es misst, ob ein Paket reproduzierbar gebaut werden kann. Es prüft nicht, ob das im Archiv liegende .deb übereinstimmt. Das ist die separate Aufgabe eines Rebuilders, der den Build mit dem veröffentlichten Artefakt vergleicht. Beide Zahlen sind nützlich. Sie beantworten unterschiedliche Fragen. Trotzdem zitieren viele Menschen die erste Zahl, als wäre sie die zweite.
Was Sie auf Ihrem eigenen Server tun sollten
Sie werden keine Distribution neu erstellen. Die auf einen gewöhnlichen Server übertragbaren Maßnahmen sind kleiner und kostengünstig.
- Fixieren Sie die Toolchain. Ein über ein Tag referenziertes Basis-Image kann sich unbemerkt ändern. Referenzieren Sie es über einen Digest und speichern Sie den Digest zusammen mit dem Release.
- Protokollieren Sie die Eingaben. Bewahren Sie die Lockfile, den Image-Digest und die Compilerversion zusammen mit dem Artefakt auf. Einen Build, dessen Umgebung Sie nicht rekonstruieren können, können Sie nicht erneut ausführen. Er kann daher nie überprüft werden.
- Führen Sie in CI zweimal einen Build aus und lassen Sie den Job fehlschlagen, wenn sich die Ausgaben unterscheiden. Das kostet einen zusätzlichen Build und erkennt Nichtdeterminismus an dem Tag, an dem er eingeführt wird, statt ein Jahr später während eines Vorfalls.
- Entfernen Sie die vom Compiler eingebetteten Pfade. Für Go entfernt
go build -trimpath -buildvcs=falsedas Build-Verzeichnis und den Versionskontrollstempel.go version -m ./appgibt aus, was tatsächlich im Binary gelandet ist. - Speichern Sie den Hash des Deployments. Wenn Sie feststellen müssen, ob das laufende Binary einer Quellrevision entspricht, kann nur dieser Eintrag die Frage beantworten.
Die CI-Prüfung umfasst vier Zeilen:
set -eu
./build.sh && mv dist/app app.1
./build.sh && mv dist/app app.2
diffoscope --text - app.1 app.2diffoscope beendet sich mit einem Fehlerstatus, wenn sich die beiden Artefakte unterscheiden. Der Job schlägt dadurch selbstständig fehl und hinterlässt eine lesbare Erklärung im Log. Das ist die gesamte Idee, auf ein Repository reduziert: Die Behauptung, dass ein Binary aus einem Quellbaum stammt, sollte von einem zweiten Rechner überprüft werden können.
FAQ
Bedeutet ein reproduzierbarer Build, dass die Software sicher ist?
Nein. Er weist nach, dass das Binärprogramm dem Quellcode entspricht, und nicht mehr. Eine in den öffentlichen Quellcode eingebrachte Hintertür wird reproduzierbar gebaut, und jeder erneute Builder bestätigt sie, weil alle denselben schädlichen Quellcode gebaut haben. Ein Paket mit einer bekannten CVE wird problemlos reproduziert und bleibt trotzdem verwundbar. Reproduzierbarkeit beseitigt eine Angreiferposition: die Build-Maschine und den Weg vom Quellcode zum Binärprogramm. Den Quellcode zu prüfen und Schwachstellen zu verfolgen, sind separate Aufgaben, die Ihnen die Reproduzierbarkeit nicht abnimmt.
Warum unterscheiden sich meine beiden Builds, obwohl sich am Quellcode nichts geändert hat?
Fast immer liegt es an einem Zeitstempel, einem Pfad oder einer Reihenfolge. Archivformate wie tar und zip speichern die Änderungszeiten von Dateien. Deshalb erzeugt ein Checkout zu einer anderen Sekunde andere Bytes. Debug-Informationen enthalten das absolute Build-Verzeichnis. Daher erzeugen /home/alice/src und /build/pkg aus identischem Code unterschiedliche Binärprogramme. Verzeichniszugriffe liefern Einträge in der Reihenfolge des Dateisystems. Deshalb können die Objektdateien in einer Linkerzeile auf einer anderen Maschine anders sortiert sein. Führen Sie diffoscope build1 build2 aus. Der Bericht nennt dann die konkrete Ursache, statt Sie raten zu lassen.
Was ist SOURCE_DATE_EPOCH, und muss ich die Variable setzen?
Dabei handelt es sich um eine standardisierte Umgebungsvariable mit einer Zahl: der letzten Änderungszeit des Quellcodes in Sekunden seit dem 1. Januar 1970 UTC. Tools, die diese Variable unterstützen, verwenden diesen Wert überall dort, wo sie sonst die Systemuhr abfragen würden. Setzen Sie sie mit export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) aus der Versionsverwaltung. Das geschieht nicht automatisch, und die Variable ist keine allgemeine Fehlerbehebung. Nur Tools, die sie implementieren, lesen sie aus. Eigene Build-Skripte müssen sie ebenfalls selbst auslesen. Ein Skript, das date aufruft, verwendet daher weiterhin die aktuelle Uhrzeit, bis Sie es ändern.
Was soll ich tun, wenn ein erneuter Builder BAD meldet?
Lesen Sie zuerst den Bericht. Ein BAD-Ergebnis bedeutet, dass ein unabhängiger erneuter Build nicht dieselben Bytes erzeugt hat. Die gewöhnliche Ursache ist eine nichtdeterministische Paketerstellung und kein Angriff. rebuilderd kann genau aus diesem Grund einen diffoscope-Bericht erzeugen. Wenn die Unterschiede auf Zeitstempel, Build-Pfade oder die Dateireihenfolge zurückzuführen sind, handelt es sich um einen Paketerstellungsfehler, den Sie melden sollten. Wenn es sich ohne eine solche Erklärung um ausführbaren Code handelt, beenden Sie die Bereitstellung dieses Builds, bewahren Sie die Artefakte auf und eskalieren Sie den Vorfall an den Herausgeber.