Onlinetickets der Deutschen Bahn

[Update: 2012 habe ich mir Tickets erneut angesehen.]

eh2010-bahn Pünktlich zu Ostern wird es endlich Zeit, mal zu zeigen, was sich denn in den Barcodes der Onlinetickets der Bahn alles an Informationen findet und daraus abzuleiten, wie wohl deren System funktioniert.

Ganz dumm haben sie sich dabei nicht angestellt, sondern – und das war der unerwartete Teil – mal ausnahmsweise alles richtig gemacht:
Die Ticketdaten werden zlib-komprimiert, dieser Blob dann DSA signiert und das alles mit dem „Aztec“ Matrixcode auf’s Ticket gebracht. Die mobilen Endgeräte prüfen (hoffentlich) die Signatur und speichern einen Kontrolldatensatz, der später mit der Datenbank abgeglichen wird, sodass sie zumindest erkennen, wenn stornierte Tickets oder das selbe Ticket mehrfach genutzt wurden (Quelle).

Letztendlich scheinen die mobilen Kontrollgeräte der vermutlich einzig verwundbare Punkt. Käme man an die Firmware ließen sich sicherlich Wege finden, diese trotz falscher Signatur von einem echten Onlineticket zu überzeugen. Ansatzpunkte wären beispielsweise Carrier-IDs die dem Gerät unbekannt sind, fehlerhafte ASN.1-codierte Signaturen oder gar Pufferüberläufe durch zu lange Payloads (Dank zlib-Kompression passt ja einiges in den Barcode).

Weitere Ressourcen:

15 Gedanken zu “Onlinetickets der Deutschen Bahn

  1. Dominik schreibt:

    Hi,

    dein Onlineticket.py liefert mir für den Signaturblock nur den code, nicht die Ausgabe:

    signature: (Sequence().setComponentByPosition(0, Integer(‘831850300058523969331890131572054925229994625947’)).setComponentByPosition(1, Integer(‘497113864828103579676642050019363524670552150813’)), ‘\x00\x00\x00’)

    Versucht hab ich es mit Python 2.5 und 2.7. Leider kenne ich mich mit Python nicht genug aus um den Fehler selbst zu fixen. Habe jedoch das gefühl das lediglich irgendwo ein print fehlt.

    Kannst du weiterhelfen?

  2. Hagen Fritsch schreibt:

    Ich weiß nicht genau, was du meinst? Die zwei Zahlen werden doch angezeigt, oder was erwartest du genau zu sehen?

  3. Daniel Marschall schreibt:

    Hier ein kleines Tutorial:

    * 1. bcTester herunterladen (nicht die Webcamedition)
    * 2. Screenshot von der Onlineticket-PDF machen. Ein Webcam-Bild wird so gut wie nicht erkannt.
    * 3. Bereich in bcTester auswählen und Aztec-Test starten. Ein Automatischer-Test funktioniert nicht (er wird “Interleaved” o.ä. erkennen anstelle Aztec-Code)
    * 4. Den Hex-Code im unteren Codefenster auswählen und in Datei “tickets” speichern.
    * 5. Python sowie Python-Modul “python-pyasn1” installieren (Paket-Name bei Debian)
    * 6. “python onlineticket.py” aufrufen

    Und noch ein Verbesserungsvorschlag:

    Die Kontrolle mittels “Personalausweis” ist “09” (gibt es seit 2010).

    Zeile 71 ist nun also

    (‘ausweis_typ’, 2, {’01’: ‘CC’, ’04’: ‘BC’, ’07’: ‘EC’, ’09’: ‘ID’}),

    anstelle

    (‘ausweis_typ’, 2, {’01’: ‘CC’, ’04’: ‘BC’, ’07’: ‘EC’}),

    habe die fehlende Angabe bereits bei Wikipedia ergänzt.

  4. Daniel Marschall schreibt:

    Noch eine Info:

    Bei dem Feld “TBD: CC-#/Ausweis-ID” steht bei mir die vollständige Personalausweis-ID und nicht die CC#. Ich habe den Personalausweis als Identifikation ausgewählt und per Kreditkarte gezahlt. Das Onlineticket ist von Juni 2011.

    Ist damit Ihr “TBD” Problem gelöst?

    Haben Sie weitere Informationen über die TBD herausgefunden?

  5. Jonas schreibt:

    Hallo,

    Ich habe ein Ticket ausgeleset mit BCTester und die Text Copy-Pasted in die Datei “tickets”. Aber wann ich die Program läuft kriege ich:
    Traceback (most recent call last):
    File “app_main.py”, line 53, in run_toplevel
    File “onlineticket.py”, line 208, in
    ots = [OT(readot(i)) for i in open(“tickets”)]
    File “onlineticket.py”, line 178, in
    readot = lambda x: ”.join([chr(int(i,16)) for i in x.split(” “)])
    ValueError: invalid literal for int() with base 16: ‘#UT010080000010-##rp├¹##lSj
    TË╗í_y#²╝#J#####Õ└¸#┘###q#Tx#┴ÔA#I###0270x#m#¤J├@#ã}##5###²7±#&í¡¡ííTí##ðC!&#ñ##
    ╠#­┼#A##¦╦■µ█of┐¦├Ù¬╠#@#g###X»8K▄═l##%@D###▓(ı©.@td@´}Pe▒#┼#5#Ï##O#ª§|##ºs╚■¯#Ï#
    r@d├##Õ▒6 

    In welche format soll die Text in tickets sein? HEX ANSI? Mit Linebrakes? Ich freue mich für alle hilfe.

  6. Hagen Fritsch schreibt:

    Hallo Jonas, das Skript erwartet die Daten im hex-Format. Dann sollte das auch klappen 😉

  7. Jonas schreibt:

    Hallo Hagen,

    Okay, noch eine Frage. Genau wie soll die Datei aussehen? Alles auf eine reihe? Alles zusammen oder mit ein delimiter (space, komma etc?

    Können Sie eine kleine Beispiel geben wie es formatiert sein soll?

    Vielen dank,
    Jonas

  8. Hagen Fritsch schreibt:

    Space-separierte Hexadezimalzahlen. Ein Ticket pro Zeile:
    z.B.:
    23 55 54 30 31 31 31 38 30 54 54 30 30 31 30 …
    23 55 54 30 31 31 31 38 30 39 30 30 30 31 30 …

  9. Jonas schreibt:

    Hmmm jetzt sieht meine Datei so aus:
    23 55 54 30 31 30 30 38…

    Ergebnis ist:

    Traceback (most recent call last):
    File “app_main.py”, line 53, in run_toplevel
    File “onlineticket.py”, line 208, in
    ots = [OT(readot(i)) for i in open(“tickets”)]
    File “onlineticket.py”, line 28, in __init__
    self.data = self.dict_read(self.fields)
    File “onlineticket.py”, line 51, in dict_read
    dat = val[3](self, res)
    File “onlineticket.py”, line 202, in
    lambda self, res: read_blocks(zlib.decompress(self.read(res[‘data_length’]))
    , read_block))
    File “onlineticket.py”, line 184, in read_blocks
    block = read_func(data, offset)
    File “onlineticket.py”, line 176, in read_block
    return block_types[data[offset:offset+6]](data, offset)
    File “onlineticket.py”, line 28, in __init__
    self.data = self.dict_read(self.fields)
    File “onlineticket.py”, line 51, in dict_read
    dat = val[3](self, res)
    File “onlineticket.py”, line 113, in read_sblocks
    typ, mod = typen.get(typ, (typ,))
    ValueError: expected length 2, got 1

    Was ist falsch? Vielen dank.

  10. Martin schreibt:

    Da mich das ganze gerade eine ganze Weile eingespannt hat, werde ich mal meine Erkenntnisse niederschreiben (ich hab nämlich keine Dokumentation für das Skript gefunden)

    * es wird die Bibliothek python-pyasn1 benötigt
    * da das Skript den zu verwendenden Parser nicht enthält, wird es als bash-Skript geparst, außer man startet es via python
    * der Code auf dem Ticket lässt sich mit dem oben verlinkten BCTester in eine Hex-Reihe umwaldeln
    * diese Reihe muss in der Datei tickets gespeichert werden, pro Zeile ein Ticket
    * am Ende einer jeden Zeile darf kein Leerzeichen stehen

    Wenn das so gemacht wird, sollte das eigentlich funktionieren :)

  11. mbirth schreibt:

    Bei einem Onlineticket dieser Tage ist der Header jetzt scheinbar “OTI” statt “#UT” und das Python-Script steigt mit “Error: strptime() argument 1 must be string without null bytes, not str (orig); Error -3 while decompressing data: invalid code lengths set (zxing)” aus.

    Außerdem muss man aufpassen, wenn man den Code mit einem UTF-8-fähigen Tool scanned, z.B. der Android-App “Barcode Scanner+”. Dann wird z.B. aus 0xA8 plötzlich 0xC2 0xA8 – was die UTF-8-Entsprechung von 0xA8 ist. D.h., man muss die Daten vorher noch von UTF-8 nach ANSI wandeln, bevor das Python-Script die frisst.

  12. DaVe schreibt:

    Hallo,

    gibt es denn eine Möglichkeit die Onlinetickets von Heute mit dem Script auszulesen? Falls ja wo muss das Script modifiziert werden?

  13. Hagen Fritsch schreibt:

    Aktuelle Tickets sollten nun wieder funktionieren. Falls nicht, am besten auf github einen Bug aufmachen und das Ticket, das nicht erkannt wird hochladen.

  14. Hagen Fritsch schreibt:

    Aktuelle Tickets sollten nun wieder funktionieren. Falls nicht, am besten auf github einen Bug aufmachen und das Ticket, das nicht erkannt wird hochladen.

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *