SSD Nodes Learn Hosting plans →
Anleitungen Matt ConnorVon Matt Connor · Aktualisiert 2026-08-21

Was reproduzierbare Builds wirklich beweisen

Eine Prüfsumme bestätigt nur die erhaltene Datei. Reproduzierbare Builds zeigen, dass sie aus genau dem lesbaren Quellcode entstand. Das sind zwei verschiedene Aussagen.

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 belegt, dass die Datei ohne Beschädigung übertragen wurde. Eine Signatur über dieser Prüfsumme belegt, dass sie von der Person stammt, die den Schlüssel besitzt. Keine von beiden Aussagen macht Angaben darüber, was vor der Erstellung des Artefakts passiert ist. Wenn der Build-Rechner des Herausgebers kompromittiert wurde, wird die schädliche Binärdatei genauso mit einer Prüfsumme versehen und signiert wie eine unveränderte Datei. Dadurch ist jede nachgelagerte Prüfung erfolgreich. Das Gleiche gilt, wenn ein Maintainer aus einem Arbeitsverzeichnis erstellt, das nie in das Repository übertragen wurde.

Genau darin besteht die Lücke. Sie können den Quellcode lesen, die Signatur prüfen 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 passiert ist.

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 von anderen geprüften Quellcode entsprachen. 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, ar und zip speichern 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/src und ein Build in /build/pkg unterscheiden 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.tar

Die 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.tar

Jetzt 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.

Unterschiede mit diffoscope auslesen

Wenn sich zwei Builds unterscheiden, zeigt sha256sum nur, dass sie unterschiedlich sind. Weitere Informationen liefert der Befehl nicht. diffoscope zeigt die Ursache in einer für Menschen lesbaren Form. 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 mehr als hundert weitere Formate.

sudo apt install -y diffoscope
diffoscope one.tar two.tar

Für das oben gezeigte 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.txt

Das 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 daher in eine Datei und öffnen Sie ihn in einem Browser:

diffoscope --html report.html build1.changes build2.changes

diffoscope beendet sich mit dem Exit-Code 0, wenn die Eingaben identisch sind, mit 1, wenn sie sich unterscheiden, und mit 2, wenn ein Fehler auftritt. Dadurch lässt es sich direkt in einen CI-Job integrieren, ohne Wrapper-Skript. Installieren Sie auf einem kleinen VPS diffoscope-minimal statt diffoscope: Das vollständige Paket installiert zahlreiche Format-Hilfsprogramme, die Sie wahrscheinlich nie aufrufen werden.

Was SOURCE_DATE_EPOCH korrigiert und wo die Wirkung endet

SOURCE_DATE_EPOCH ist eine Umgebungsvariable, die eine Zahl enthält: den Zeitpunkt der letzten Änderung am Quellcode, angegeben als Sekunden seit dem 1. Januar 1970 UTC. Ein Build-Tool, das die Variable unterstützt, verwendet diesen Wert überall dort, wo es sonst die aktuelle Zeit vom Betriebssystem abfragen würde. Setzen Sie den Wert aus der Versionsverwaltung, damit er dem Quellcode und nicht dem Build folgt:

export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)

In einem Debian-Paket exportiert debhelper die Variable für Sie aus dem Changelog. Manuell setzen Sie sie in debian/rules so:

export SOURCE_DATE_EPOCH ?= $(shell dpkg-parsechangelog -STimestamp)

Die Unterstützung gilt pro Tool und nicht global. cmake 3.8 und höher, gcc 7 und höher, rpm ab Version 4.13 sowie Docker buildx 0.10 und höher lesen die Variable aus. Ihre eigenen Skripte tun dies 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. Überschreiben Sie daher niemals den Wert, den der Aufrufer vorgegeben hat.

Bei Container-Images besteht dasselbe Problem in einer anderen Form. Docker buildx 0.10 und höher übernimmt SOURCE_DATE_EPOCH aus Ihrer Shell als Build-Argument in den Build. Für die Zeitstempel der Dateien innerhalb der Layer muss der Exporter sie neu schreiben. BuildKit unterstützt dies ab Version 0.13. 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 .

Testen Ihres eigenen Builds mit reprotest

reprotest erstellt zweimal aus demselben Quellcode einen Build und verändert absichtlich die Umgebung zwischen den beiden Builds. Anschließend vergleicht es die Ergebnisse. Diese Variationen sind der eigentliche Zweck des Tools. Standardmäßig variiert es den Build-Pfad, die Uhrzeit, die Zeitzone, die Locale, die umask, den Hostnamen, den Benutzer und die Gruppe, die Anzahl der CPUs, das Home-Verzeichnis und die Dateireihenfolge.

sudo apt install -y reprotest
reprotest . -- null

Alles nach -- wählt das Backend für die Build-Umgebung aus, und null steht für das System, auf dem Sie gerade arbeiten. Fügen Sie -vv -d hinzu, damit die temporären Verzeichnisse zur Untersuchung erhalten bleiben, wie in reprotest . -vv -- null -d. Verwenden Sie reprotest auto -- null, damit reprotest selbst ermittelt, welche Art von Quellbaum es vor sich hat.

Für einige Variationen sind Berechtigungen oder zusätzliche Pakete erforderlich. Sie schlagen mit einer eindeutigen Fehlermeldung fehl, wenn sie nicht ausgeführt werden können. Deaktivieren Sie diese Variationen, statt den gesamten Vorgang als root auszuführen:

reprotest --vary=-user_group,-domain_host,-fileordering auto -- null

Alles, 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.buildinfo

debrebuild 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 rebuilderd

Die 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?

ChartDebian packages reproducible in the test framework, amd64
The data behind this chart
[
  {
    "label": "unstable",
    "percent_reproducible": 94.2,
    "tested_count": "41,163"
  },
  {
    "label": "forky",
    "percent_reproducible": 93.4,
    "tested_count": "39,059"
  },
  {
    "label": "experimental",
    "percent_reproducible": 67.0,
    "tested_count": "588"
  }
]

Am Tag der Veröffentlichung dieses Beitrags lag der Anteil reproduzierbarer Builds in unstable auf amd64 bei 94.2% von 41,163 getesteten Paketen. In Experimental waren es 67.0%. Dort wurde jedoch eine deutlich kleinere und deutlich neuere Stichprobe von 588 Paketen untersucht. Das ist bei Paketen zu erwarten, deren Fehler noch nicht vollständig behoben wurden.

Diese Zahlen stammen von der Debian-Seite auf tests.reproducible-builds.org. Sie wurde am 2026-08-18 abgerufen und trug den Zeitstempel "Last update: 2026-08-18 16:02 UTC". Die Werte ändern sich. Verwenden Sie den Tracker, statt diesen Absatz in sechs Monaten zu zitieren.

Ein Vorbehalt ist wichtiger als der Prozentwert. Dieses Framework baut jedes Paket zweimal auf der eigenen Hardware und variiert die Umgebung zwischen den beiden Builds. Anschließend vergleicht es die beiden eigenen Ergebnisse. Es misst, ob ein Paket reproduzierbar gebaut werden kann. Es prüft nicht, ob der im Archiv liegende .deb mit dem veröffentlichten Artefakt übereinstimmt. Diese Prüfung ist die separate Aufgabe eines Rebuilders, der das Ergebnis mit dem veröffentlichten Artefakt vergleicht. Beide Zahlen sind nützlich. Sie beantworten unterschiedliche Fragen. Trotzdem wird die erste Zahl oft so zitiert, 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=false das Build-Verzeichnis und den Versionskontrollstempel. go version -m ./app gibt 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.2

diffoscope 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.

#reproducible-builds#supply-chain#diffoscope#debian#verification