Xorino Log

← Hilfe & Steuerwissen

Fahrtenbuch selbst prüfen: die Prüfkette nachrechnen

Ein Fahrtenbuch ist ein Aufbewahrungsdokument. Es kann Jahre nach der letzten Fahrt gebraucht werden, und dann stellt sich eine einzige Frage: Ist das noch derselbe Stand, oder hat jemand nachträglich etwas geändert?

Viele Apps beantworten diese Frage mit einem Siegel — einem Wort auf der Produktseite, das man glauben muss. Diese Seite beantwortet sie anders: Sie beschreibt das Verfahren so genau, dass du es auf deinem eigenen Rechner nachrechnen kannst, ohne uns und ohne Internetverbindung. Ein Prüfweg, der vom Hersteller abhängt, prüft den Hersteller nicht mit.

Sie ist zugleich die Seite, auf die der Prüfsummen-Abschnitt eines exportierten Fahrtenbuchs verweist.

Was die Kette leistet — und was sie nicht leistet

Jede Zeile im Änderungsprotokoll trägt eine Prüfsumme, die den eigenen Inhalt und die Prüfsumme der Vorgängerzeile abdeckt. Ein einziges geändertes Zeichen bricht deshalb nicht nur seine Zeile, sondern jede Zeile danach.

Was das belegt: Keine einzelne Aufzeichnung wurde nachträglich verändert, eingefügt oder herausgenommen, ohne dass es sichtbar wird.

Was es nicht belegt: dass niemand die Kette als Ganzes neu berechnet hat. Die Kette kommt ohne Schlüssel aus — wer die Software kennt, kann sie vollständig nachbauen. Das ist keine Schlamperei, sondern die Lage bei lokaler Speicherung: Ein Schlüssel, der auf demselben Gerät liegt wie die Daten, sichert nichts, was das Gerät nicht ohnehin hat.

Für den steuerlichen Zweck reicht das: Verlangt wird, dass Änderungen dokumentiert sind — verworfen wird die unsichtbare Änderung.

Die Grenze, die du kennen musst

Es gibt eine Manipulation, die diese Prüfung nicht findet, und sie steht hier oben und nicht im Kleingedruckten.

Werden Zeilen am Ende der Kette entfernt, fällt das beim Nachrechnen nicht auf. Was übrig bleibt, beginnt weiterhin bei 1, ist lückenlos und ergibt an jeder Stelle die richtige Prüfsumme — eine gekürzte Kette ist eine gültige kürzere Kette. Das wiegt schwerer, als es klingt, denn am Ende stehen die neuesten Einträge und Korrekturen.

Der Grund ist grundsätzlich und lässt sich nicht wegprogrammieren: Eine gekürzte Kette ist kein Fälschungsprodukt, sondern ein echter Ausschnitt — Zeichen für Zeichen der Zustand, den die Kette zu einem früheren Zeitpunkt wirklich hatte. Auch eine Signatur hilft dagegen nicht, denn was damals unterschrieben wurde, war damals wahr (siehe unten).

Zwei Dinge engen die Lücke ein:

Die Zahl auf dem Bericht ist die Position in der Kette. Wer sie mit einem späteren Auszug vergleicht, sieht, ob etwas fehlt.

Beide Einschränkungen dazu, unverkürzt: Der Anker wirkt erst ab dem ersten eingereichten Bericht, und er wirkt nur, wenn jemand die beiden Zahlen vergleicht. Vorher und ohne Vergleich bleibt das Kürzen des Endes unentdeckbar.

Die Signatur — was sie sagt und was nicht

Die Spitze der Kette kann vom Gerät signiert werden: ECDSA über einen Schlüssel im hardwaregestützten Schlüsselspeicher des Telefons. Signatur, öffentlicher Schlüssel und Verfahrensname reisen mit dem Export, die Prüfung braucht also nichts von uns.

Signaturen sind Kontrollpunkte: Eine Signatur über die Spitze deckt alles davor mit ab, weil die Spitze jede Vorgängerzeile bindet. Dass eine Signatur fehlt, ist ein gültiger Zustand und kein Defekt — der Schlüssel kann das Gerät nicht verlassen und übersteht keinen Gerätewechsel. Nach einer Wiederherstellung kommt die Kette also unbeschädigt und ab dieser Stelle unsigniert an; ältere Signaturen bleiben gegen den Schlüssel prüfbar, mit dem sie entstanden sind.

Zwei Grenzen, ausdrücklich:

Welche Fassung des Formats du vor dir hast

Der Export nennt zwei Nummern, und sie beantworten verschiedene Fragen:

Feldsagt
format_versionwie die Datei aufgebaut ist — welche Blöcke es gibt und wie sie heißen (zum Lesen)
chain_format_versionwie eine gehashte Zeile aufgebaut ist — Feldreihenfolge, Schreibweisen, welche Felder es gibt (zum Nachrechnen)

Diese Seite beschreibt Kettenform 5. Ein Export, der gar keine Nummer nennt, ist Form 1 — die gab es nur vor der Veröffentlichung.

Für das Nachrechnen der Zeilen macht das nichts aus: Form 1 bis 5 rechnen sich gleich. Was sich zwischen ihnen geändert hat, sind die Datensätze, die eine Zeile mitführt — aber die Kette hasht diese Nutzlast als Text, ihr innerer Aufbau erreicht die Rechnung nie. Form 2 hat zwei bestehenden Satzarten je ein Feld gegeben (category_source in der Fahrt, full_tank im Kilometerstand-Referenzpunkt), Form 3 hat zwei neue Satzarten hinzugefügt (origin und restore, unten erklärt), Form 4 hat der Fahrt ein weiteres Feld gegeben (distance_source, unten erklärt), Form 5 dem Kilometerstand-Referenzpunkt (reading_source, unten erklärt). Eine wirklich neue Zeilenform entstünde erst, wenn sich die chg-Zeile selbst ändert, und das ist bisher einmal passiert. Auch ein neues Wort für die Ereignisart (RECONCILE, unten erklärt) ist keine neue Form: Die Werkzeuge prüfen Zeilen, kein Vokabular.

Ein Unterschied, der nur beim Wiederaufbau zählt: Drei Formen haben einen bestehenden Datensatz geändert, statt ein Feld danebenzustellen — Form 2, Form 4 und Form 5. Form 3 ist die einzige, die nur neue Satzarten daneben gestellt hat. Für die Datei, die du in der Hand hältst, ändert das nichts — sie trägt ihren Text mit sich. Für eine Kette, die aus den Datensätzen neu aufgebaut wird, schon: Dabei entsteht ein anderer Text und damit ein anderer Hash. Das gilt für alle drei gleich: unter keiner von ihnen reproduziert ein Hash den Wert der Vorgängerform. Was dann passiert, steht unten unter „Wenn der Bestand aus einem Abzug stammt".

Und die beiden Werkzeuge lesen jede Zeilenform, die es je gab. Eine aktuelle Fassung prüft also eine Datei jeden Alters; niemand muss sich ein altes Werkzeug besorgen. Die Reihenfolge, in der sie entscheiden, trennt dabei Prüfen von Raten:

  1. Die Datei nennt eine Form, die das Werkzeug kennt → diese Form gilt. Scheitert die Kette gegen sie, ist die Kette gebrochen. Es wird dann nicht weiterprobiert, bis irgendeine Form passt — das würde die Nummer zur Zierde machen.
  2. Die Datei nennt keine Form → die bekannten werden durchprobiert, und das Ergebnis sagt, welche gepasst hat.
  3. Die Datei nennt eine neuere Form, als das Werkzeug kennt → es sagt das und prüft trotzdem. Meist gelingt es, weil geänderte Datensätze die Zeile ja nicht berühren; das Ergebnis weist dann darauf hin, dass die Nutzlasten anders aufgebaut sein können, als das Werkzeug sie liest.

Was in keinem dieser Fälle passiert: „Kette gebrochen" als Auskunft über ein Problem des Werkzeugs. Genau dafür gibt es die Nummer. Ohne sie würde ein Werkzeug, das eine unbekannte Zeilenform einfach durchrechnet, bei jeder Zeile einen anderen Hash bekommen und einen Befund über deine Daten melden — ausgerechnet in dem Moment, in dem du die Datei jemandem vorlegst.

So ist eine Zeile aufgebaut

Damit eine Prüfsumme reproduzierbar ist, muss jede Zeile genau eine Textform haben, die gehasht wird. Zuerst ein Kürzel, dann die Felder in fester Reihenfolge, verbunden durch |:

chg|1|1755691500000|6:CREATE|4:TRIP|17|∅|10:trip|17|42|∅

Die Feldreihenfolge ist Teil des Formats. Ein Feld hinzuzufügen, zu entfernen oder zu verschieben ändert jede jemals geschriebene Prüfsumme.

Wie die einzelnen Werte geschrieben werden

ArtSchreibweiseBeispiel
Ganzzahldezimal, ohne Zusatz17
ZeitpunktMillisekunden seit 1970, dezimal1755691500000
Wahrheitswert1 / 00
Aufzählungswertüber den Namen, mit Längenangabe8:BUSINESS
Textmit Längenangabe in UTF-8-Bytes9:Meier AG
DateiSHA-256 der Bytes in Kleinbuchstaben, mit Längenangabe64:0390…fb81
kein Wert∅ (U+2205, UTF-8 E2 88 85)∅

Vier dieser Festlegungen sind Entscheidungen und keine Zufälle:

In einer geketteten Zeile kommt keine Kommazahl vor. Fließkommazahlen haben keine Schreibweise, die einen Bibliothekswechsel unbeschadet übersteht. Deshalb ist jede Größe eine Ganzzahl einer kleinen Einheit: Strecken in Millimetern, Koordinaten in Zehnmillionstel Grad, Kraftstoff in Millilitern, Beträge in Cent plus Währungskürzel.

Der Kettenschritt

Gehasht werden ausschließlich die Zeilen des Änderungsprotokolls — für die Prüfung wird keine weitere Tabelle gebraucht. Jede dieser Zeilen hat die Form:

chg | lfd. Nummer | Zeitpunkt | Ereignisart | Objektart | Objekt-Nummer
    | Stand vorher | Stand nachher | Grund

„Stand vorher" und „Stand nachher" tragen die Textform des Datensatzes selbst, mit Längenangabe wie jeder andere Text. Beim Anlegen ist „vorher" leer, beim Verwerfen „nachher". Der Grund ist meist leer und beim Verwerfen einer Fahrt Pflicht.

Die Prüfsumme der Vorgängerzeile ist kein Feld dieser Zeile. Sie geht über den Hash-Schritt ein — stünde sie zusätzlich im Text, wäre der Vorgänger doppelt gebunden und die beiden Kopien könnten auseinanderlaufen:

Prüfsumme(Zeile) = SHA-256( Prüfsumme_Vorgänger + "|" + Textform(Zeile) )

Ergebnis als Hexadezimalzahl in Kleinbuchstaben. Für die erste Zeile ist die Prüfsumme des Vorgängers die leere Zeichenkette.

Nachrechnen in drei Schritten

Lies die Zeilen nach laufender Nummer sortiert und prüfe je Zeile:

  1. Die laufende Nummer ist Vorgänger + 1, beginnend bei 1 — keine Sprünge. Ein Sprung heißt, dass eine Zeile entfernt oder eingefügt wurde.
  2. Die hinterlegte Vorgänger-Prüfsumme stimmt mit der Prüfsumme der vorigen Zeile überein.
  3. Neu berechnet ergibt sich genau die gespeicherte Prüfsumme.

Melde den ersten Fehler, nicht alle. Nach einer geänderten Zeile scheitert jede folgende zwangsläufig — eine Zählung würde „alles ab Zeile 12 ist falsch" sagen, während die ehrliche Aussage „Zeile 12 ist falsch" lautet.

Ereignis- und Objektarten

Ereignisarten sind CREATE · CLASSIFY · CORRECT · MERGE_REFERENCE · RECONCILE · SPLIT · EXPORT · RESTORE · RECOMPUTED · HANDOVER · VOID.

RECONCILE (seit 13.09.2026) ist das eine Ereignis, bei dem „Stand vorher" und „Stand nachher" denselben Kilometerstand-Referenzpunkt zeigen: Der abgelesene Stand wurde gegen die Summe des Buches gehalten, und die beiden Felder computed_reading_mm und delta_mm sind von ∅ zu einem Wert geworden. Sonst ändert sich in der Zeile nichts, und es passiert je Referenzpunkt höchstens einmal. Ein Export von vor diesem Datum trägt das Paar schon auf der CREATE-Zeile und kennt kein RECONCILE — beide Lesarten sind richtig.

Objektarten sind TRIP (Fahrt) · STAY (Aufenthalt) · VEHICLE (Fahrzeug) · ODOMETER_REFERENCE (Kilometerstand-Referenzpunkt) · GAP (Lücke).

Die Felder der einzelnen Datensätze

Diese Reihenfolgen brauchst du nur, um „Stand vorher" und „Stand nachher" zu lesen; die Unversehrtheit der Kette hängt nicht an ihnen. Schreibweisen wie oben.

trip     id · started_at · ended_at · start_lat_e7 · start_lon_e7 · end_lat_e7
         · end_lon_e7 · start_place_label · end_place_label · destination_source
         · odometer_start_mm · odometer_end_mm · measured_distance_mm
         · distance_source · category · category_source · transport_mode
         · business_partner · purpose
         · detour_reason · vehicle_id · not_my_vehicle · polyline · receipt_hash
         · superseded_by · derived_from · chain_seq

stay     id · arrived_at · departed_at · lat_e7 · lon_e7 · place_label · customer
         · chain_seq

vehicle  id · display_name · opened_at · opening_reading_mm
         · opening_reading_source · closed_at · closing_reading_mm
         · is_premium_parallel

vehid    id · vehicle_id · type · value · display_name · confirmed_at
         · first_seen_at · chain_seq

odoref   id · vehicle_id · recorded_at · kind · entered_reading_mm
         · reading_source · computed_reading_mm · delta_mm · fuel_millilitres
         · full_tank · amount_minor · amount_currency · superseded_by · note
         · chain_seq

gap      id · started_at · ended_at · reason · distance_mm · resolved_as · note
         · chain_seq

export   id · created_at · period_start · period_end · app_version · algo_version
         · lines · chain_seq

origin   previous_head · previous_length · previous_format
restore  previous_head · previous_length · previous_format · signature_state
         · key_fingerprint

Die letzten beiden sind keine Datensätze, sondern die Eröffnungszeile einer Kette, die eine andere abgelöst hat — mehr dazu im nächsten Abschnitt.

Sieben Stellen lohnen den zweiten Blick, weil dort die Ehrlichkeit des Buches sitzt:

Wenn der Bestand aus einem Abzug stammt

Eine Kette lässt sich nicht über eine Änderung der kanonischen Form hinweg fortführen — die alten Prüfsummen reproduzieren sich dann nicht mehr. Sie wird stattdessen aus den exportierten Datensätzen neu aufgebaut, und die erste Zeile der neuen Kette sagt, was passiert ist. Dafür gibt es die zwei Zeilen von oben:

Welcher Fall vorliegt, sagt der Mensch, der die Datei einspielt — die App rät es nicht.

Warum die Unterscheidung überhaupt nötig ist, ist der wichtigste Satz auf dieser Seite: Wer eine Exportdatei vor dem Einspielen bearbeitet, bekommt anschließend trotzdem eine fehlerfreie Kette. Eine Kette belegt innere Stimmigkeit, nie Herkunft. Deshalb behauptet die restore-Zeile nichts, sondern hält einen Befund fest: was von der Gerätesignatur des abgebenden Geräts übrig ist.

Der Satz gilt nicht nur für den Fall, an den man zuerst denkt. Eine unvollständige Datei hat dieselbe Wirkung wie eine bearbeitete — und sie entsteht ohne böse Absicht: Wenn ein Feld beim Ausgeben fehlt, wird die Kette beim Einspielen sauber über die Daten aufgebaut, die da sind. Sie ist dann fehlerfrei und beschreibt trotzdem etwas anderes als das ursprüngliche Buch. Deshalb prüft verify_chain die Verkettung der Zeilen, die vorliegen — nicht, ob alle Zeilen und alle Felder vorliegen, die es einmal gab. Für den Vergleich zweier Bestände ist der Kopf-Hash das Mittel, nicht das Prüfergebnis: Zwei Bestände beschreiben dieselben Daten, wenn ihre Ketten auf denselben Kopf laufen.

Der Umkehrschluss gilt nicht — und ausgerechnet hier nicht. Ein Wiederaufbau eröffnet eine neue Kette, und ihre erste Zeile ist die origin- oder restore-Zeile. Damit rückt jede Position um eins, und kein Hash stimmt mehr mit dem alten Bestand überein, obwohl dieselben Fahrten darin stehen. Über einen Wiederaufbau hinweg sagen zwei verschiedene Köpfe also nichts über die Daten; das Mittel taugt zwischen zwei Ausgaben derselben Kette.

Und die neue Kette darf kürzer sein als die alte. Ein Wiederaufbau spielt die Geschichte nicht nach, er schreibt jeden Datensatz einmal — in dem Zustand, in dem er geendet hat. Zeilen, die nur eine Zwischenstufe festhalten, etwa ein nachgereichter Abgleich (RECONCILE), entstehen dabei nicht noch einmal: In einem gemessenen Fall wurden aus 129 Gliedern 124, ohne dass ein einziger Datensatz fehlte. Weniger Glieder sind also kein Verlust — sie sind der sichtbarste Beleg dafür, dass ein Wiederaufbau die Datensätze mitnimmt und die Geschichte nicht.

Was stattdessen trägt: die kanonischen Zeilen ohne ihre Position. Nimm aus jeder der beiden Dateien den payload_after aller Zeilen des Änderungsprotokolls und dann je Zeile:

  1. alles nach dem letzten | streichen — das ist die Kettenposition und sonst nichts;
  2. die Ergebnisse als Menge vergleichen, nicht als Liste.

Gleiche Mengen heißt: dieselben Datensätze. Drei Dinge, an denen dieser Weg hängt:

Die Grenze: Der Vergleich gilt innerhalb einer Kettenform. Über einen Formwechsel hinweg unterscheiden sich die kanonischen Zeilen zwangsläufig — das ist ja der Grund, aus dem neu aufgebaut wird. Er zerfällt dabei aber nicht, sondern wird genauer: In einem gemessenen Fall (36 Datensätze, Wechsel von Form 3 auf 4) blieben 27 identisch, und die 9 Unterschiede waren genau die 9 Fahrten — unterschieden um genau das eine Feld, das die neue Form eingefügt hatte. Beim Wechsel von Form 4 auf 5 dasselbe Bild an einem größeren Buch: von 113 Satzzeilen blieben 100 identisch, die 13 abweichenden waren alle Kilometerstände, und sie stimmten wieder überein, sobald man das neue Feld herausnahm. In beiden gemessenen Fällen zeigte sich die Formänderung also genau dort, wo das neue Feld sitzt — und sonst nirgends. Auf eine Satzart darfst du das aber nicht verallgemeinern: Form 2 hat gleich zwei berührt. Welche Felder eine Form angefasst hat, sagt die Formhistorie in FORMAT.md.

Und was er nicht kann: Er vergleicht Datensätze, nicht Geschichte. Ein Wiederaufbau nimmt das alte Änderungsprotokoll nicht mit — seine Zeitpunkte und seine Zeilen beschreiben eine kanonische Form, die es nicht mehr gibt. Zwei Dateien können also dasselbe Buch enthalten und verschiedene Vorgeschichten dokumentieren; für die ältere Geschichte bleibt die ältere Datei die Quelle.

Eine Ausnahme, und sie ist wichtig: Ein Verwerfen überlebt den Umzug. Der Aufbau gibt jede verworfene Zeile neu aus, mit ihrem ursprünglichen Grund. Andernfalls wäre eine Fahrt, die jemand als „hat nie stattgefunden" zurückgenommen hat, nach dem nächsten Formwechsel still wieder mitgezählt worden — der Umzug hätte eine Korrektur rückgängig gemacht, ohne dass es irgendwo steht. Was verloren geht, ist der Zeitpunkt des Verwerfens, nicht seine Wirkung. Genau deshalb nennt die origin-Zeile Kopf und Länge dessen, was sie ersetzt — sie ist das Bindeglied, das der Kopf-Hash nicht mehr sein kann.

signature_stateheißt
VALIDDer Abzug trug eine Gerätesignatur über genau den Kopf, den er nennt, und sie ging auf
INVALIDEine Signatur war da und ging nicht auf — der eine Wert, bei dem du anhalten solltest
NONEKeine Signatur. Nach einem Gerätewechsel der Normalfall, weil der Signierschlüssel sein Gerät nie verlässt: eine schwächere Aussage, keine verdächtige
UNCHECKEDVorhanden, hier nicht geprüft — „wir haben nicht nachgesehen" ist etwas anderes als „da war nichts"

Zwei Festlegungen dahinter, die du kennen solltest:

Ein Export nach einer Wiederherstellung zieht das alles nach oben in einen provenance-Block neben chain — einschließlich der gehashten Zeile im Wortlaut. Damit ist die Zusammenfassung gegen die Kette nachprüfbar und nicht bloß zu glauben. Ein Buch, das von der ersten Zeile an auf einem Gerät geführt wurde, hat den Block nicht; er entsteht nur, wenn es etwas zu sagen gibt.

Womit du prüfst

Du brauchst kein Werkzeug von uns. Das oben Beschriebene reicht aus, um die Prüfung mit jedem SHA-256-Programm nachzubauen — das ist der Zweck dieser Seite und der Grund, warum sie so ausführlich ist.

Bequemer geht es mit den beiden fertigen Fassungen:

Beide Werkzeuge stehen zum Herunterladen bereit: verify-chain.html für den Browser und verify_chain.py für die Kommandozeile. Dazu die maßgebliche Formatbeschreibung FORMAT.md — englisch, ausführlicher als diese Seite, und das Dokument, auf das der Prüfsummen-Abschnitt deines Exports verweist.

Die Browserfassung ist eine einzige Datei: herunterladen, mitnehmen, auf einem Rechner ohne Internet öffnen — sie lädt nichts nach und schickt nichts weg.

Diese Testfälle sind der Grund, warum wir dem Ergebnis selbst trauen: Sie wurden nach den Regeln dieser Seite von Hand gebaut und mit einem Standard-Kommandozeilenwerkzeug gehasht, nicht aus einer Umsetzung übernommen. Dieselben Testfälle liegen in der App, im Python-Skript und hinter dem Selbsttest der Browserseite. Stimmen alle überein, sind vier voneinander unabhängige Umsetzungen einer Meinung.

Was ein „Kette intakt" wert ist

Genau das, was oben steht, und keinen Satz mehr: Das Vorgelegte stimmt in sich. Es heißt nicht, dass es alles ist — dafür brauchst du die Kettenposition auf einem Bericht, den jemand anderes aufbewahrt.

Uns ist diese Unterscheidung wichtiger als ein starkes Wort auf der Produktseite. Ein Fahrtenbuch wird in dem Moment gebraucht, in dem jemand es anzweifelt; wer dann feststellt, dass die Zusage weiter reichte als das Verfahren, steht schlechter da als jemand, der die Grenze von Anfang an kannte.

Weiterlesen: Wozu ein Fahrtenbuch — und was es überall leisten muss erklärt die drei Prinzipien, an denen ein Fahrtenbuch gemessen wird. Fahrtenbuch-App und Datenschutz stellt dieselbe Frage für deine Standortdaten.


Xorino Log schreibt jede Änderung als eigene Zeile in diese Kette, auf dem Gerät und ohne Konto. Der Export deines Bestands samt Änderungsprotokoll ist kostenlos, auch ohne Premium, und die Datei benennt selbst, was sie nicht enthält.

Kein Steuerrat. Dieser Artikel erklärt allgemeine Regeln und ersetzt keine steuerliche Beratung im Einzelfall. Ob und wie sie auf deine Situation zutreffen, klärst du mit deiner Steuerberaterin oder deinem Steuerberater. Stand: 05.09.2026 — Steuerrecht ändert sich; prüfe bei älteren Artikeln die Quellen.

Xorino Log ist in Entwicklung; der Release ist für Ende 2026 geplant. Was auf dieser Seite über die App steht, gehört zum geplanten Funktionsumfang.