Skip to content
Zurück zum Blog
Tutorials

CRC-16-Varianten: Warum MODBUS, CCITT und XMODEM abweichen

Gleiche Bytes, vier verschiedene CRC-16-Ergebnisse. Online nachvollziehen, wie poly, init, refin, refout und xorout MODBUS von CCITT-FALSE und XMODEM trennen.

13 Min. Lesezeit

CRC-16-Varianten: Warum MODBUS, CCITT und XMODEM abweichen

CRC-16 bezeichnet keinen einzelnen Algorithmus, sondern eine ganze Familie, deren Mitglieder einander widersprechen. Geben Sie dieselben Bytes an MODBUS, an CCITT-FALSE und an XMODEM, und Sie bekommen drei völlig unabhängige 16-Bit-Zahlen zurück.

Fünf Konstanten entscheiden, welches Mitglied Sie gerade ausführen: poly, init, refin, refout und xorout. Ändern Sie eine davon, und die Ausgabe ändert sich vollständig, ohne jede teilweise Ähnlichkeit, die Sie vorwarnen würde. Das ist bereits die ganze Antwort auf „Warum stimmt meine CRC nicht mit der des Geräts überein?“: Sie und das Gerät führen unterschiedliche Varianten aus, und keine der beiden Seiten hat festgehalten, welche.

Der Rest dieses Artikels arbeitet die Einzelheiten ab, angefangen bei der Frage, was die fünf Parameter im Register eigentlich anrichten.

Jeder Wert in diesem Leitfaden stammt aus einem parametrisierten CRC-Modell, das unter macOS auf Python 3.14.7 gelaufen ist. Vier Quellen haben die Werte bestätigt: zlib.crc32 und binascii.crc_hqx aus der Standardbibliothek, die veröffentlichten Prüfwerte im RevEng-CRC-Katalog sowie die Herstellerdokumentation zu jedem einzelnen Parametersatz.

1. Gleicher Frame, vier CRC-16-Ergebnisse

Hier ist eine echte Modbus-RTU-Anfrage. Slave 01, Funktionscode 03 (Holding-Register lesen), Startadresse 0x0000, Anzahl 0x000A:

01 03 00 00 00 0A

Sechs Bytes, durch vier Varianten geschickt:

VarianteErgebnisAuf der Leitung (niederwertiges Byte zuerst)
CRC-16/MODBUS0xCDC5C5 CD
CRC-16/IBM-3740 (CCITT-FALSE)0x042828 04
CRC-16/XMODEM0x0A3838 0A
CRC-16/ARC0xD6C5C5 D6

Zwischen diesen vier Werten besteht keinerlei Zusammenhang. Sie liegen nicht um eine Konstante auseinander, und kein Vertauschen von Nibbles oder Bytes überführt einen in den anderen. Zwei von ihnen beginnen auf der Leitung mit demselben Byte, was Zufall ist und keine Beziehung.

Die praktische Folge: Die Zeichenkette „CRC-16“ in einem Datenblatt trägt fast keine Information. Sie sagt Ihnen, dass die Ausgabe 16 Bit breit ist, und hört dort auf. Wenn Sie ein Ergebnis in einem anderen Zahlensystem betrachten möchten, während Sie es mit einem Gerät vergleichen, das binär ausgibt, wandelt der Zahlensystem-Konverter einen 16-Bit-Wert zwischen Hexadezimal und Binär um, ohne dass Sie über das Auffüllen nachdenken müssen.

2. Fünf Parameter entscheiden alles

Unter jeder Variante steckt dieselbe Maschine: ein Schieberegister, das ein Bit nach dem anderen frisst und immer dann eine Konstante hinein-XORt, wenn oben eine 1 herausfällt. Die Parameter bestimmen, was in dieses Register hineingeht, in welche Richtung die Bits wandern und was am Ausgang noch mit ihnen geschieht.

Vierzehn Zeilen Python genügen für die gesamte Maschinerie. Die Implementierung arbeitet bitweise, ist also langsam, dafür lesbar, und sie reproduziert jeden Wert in diesem Artikel:

def crc(data, width, poly, init, refin, refout, xorout):
    top = 1 << (width - 1)
    mask = (1 << width) - 1
    reg = init
    for byte in data:
        if refin:
            byte = int(f"{byte:08b}"[::-1], 2)
        reg ^= byte << (width - 8)
        for _ in range(8):
            reg = ((reg << 1) ^ poly) if reg & top else (reg << 1)
            reg &= mask
    if refout:
        reg = int(f"{reg:0{width}b}"[::-1], 2)
    return reg ^ xorout

Die Ausgabe der Engine lässt sich gegen die Python-Standardbibliothek prüfen. zlib.crc32 und binascii.crc_hqx liefern jeweils eine unabhängige Antwort, und alle drei stimmen überein:

zlib.crc32(b"123456789")               = 0xCBF43926   engine above = 0xCBF43926   MATCH
binascii.crc_hqx(b"123456789", 0x0000) = 0x31C3       CRC-16/XMODEM = 0x31C3      MATCH
binascii.crc_hqx(b"123456789", 0xFFFF) = 0x29B1       CCITT-FALSE   = 0x29B1      MATCH

poly: zwei Lager, 0x8005 und 0x1021

Das Polynom ist die Konstante, die zurück in das Register ge-XORt wird. Es wird in der „normalen“ Form notiert, mit implizitem obersten Bit, sodass 0x8005 für x^16 + x^15 + x^2 + 1 steht und 0x1021 für x^16 + x^12 + x^5 + 1.

Die Schleife aus Schieben und XOR ist eine schriftliche Polynomdivision über GF(2). Jedes Bit der Nachricht ist ein Schritt der Division, das Polynom ist der Divisor, und das Register hält den laufenden Rest.

Nahezu jede CRC-16, die Ihnen begegnet, verwendet eines dieser beiden. 0x8005 deckt ARC, MODBUS und USB ab. 0x1021 deckt das gesamte CCITT-Durcheinander ab, dazu XMODEM, KERMIT und die RFID-Varianten. Genau deshalb identifiziert das Polynom allein niemals eine Variante.

init: der Startwert des Registers

Zwei Werte dominieren, 0x0000 und 0xFFFF. Der Unterschied wiegt schwerer, als er aussieht. Starten Sie bei null, dann lässt ein Nullbyte das Register bei null, also erzeugen 00 00 01 und 01 identische CRCs. Eine Nachricht, die unterwegs führende Nullen gewinnt oder verliert, besteht die Prüfung trotzdem. Der Start bei 0xFFFF beseitigt diesen blinden Fleck, und genau deshalb verwenden Modbus, CCITT-FALSE und USB ihn.

refin und refout: Bit-Reihenfolge, nicht Byte-Reihenfolge

refin kehrt die acht Bits jedes Eingabebytes um, bevor es in das Register gelangt. refout kehrt das fertige Register vor dem letzten XOR um. Hardware, die Daten LSB-zuerst hinausschiebt, bekommt diese Spiegelungen geschenkt, weshalb die gespiegelten Varianten typischerweise jene sind, die in seriellen Protokollen entstanden sind.

Dieses Parameterpaar wird am häufigsten mit der Endianness verwechselt, obwohl es auf einer ganz anderen Ebene arbeitet. Abschnitt 6 trennt beide sauber voneinander.

xorout: das abschließende XOR

Der letzte Schritt, angewendet auf das Register nach refout. Bei CRC-16 üblicherweise 0x0000 oder 0xFFFF, in der gesamten CRC-32-Familie 0xFFFFFFFF. Es ist der Parameter, den man am billigsten falsch macht, denn eine Abweichung hier sieht exakt so aus wie eine Abweichung an jeder anderen Stelle.

width: 8, 16 oder 32 Bit

Die Breite legt die Obergrenze der Erkennung fest. Eine CRC der Breite n fängt jeden Burst-Fehler bis zu einer Länge von n Bit und übersieht eine zufällige Verfälschung mit einer Wahrscheinlichkeit von etwa 2^-n. Das ist 1 zu 65.536 bei CRC-16 und 1 zu 4,3 Milliarden bei CRC-32.

Wie sich das auswirkt, zeigt ein Durchlauf ausgehend von CRC-16/XMODEM, bei dem jeweils genau ein Parameter geändert wird, gegen die Standard-Prüfeingabe 123456789:

baseline CRC-16/XMODEM                        = 0x31C3
init 0x0000 -> 0xFFFF   (becomes CCITT-FALSE) = 0x29B1
refin/refout -> true    (becomes KERMIT)      = 0x2189
xorout 0x0000 -> 0xFFFF                       = 0xCE3C
poly 0x1021 -> 0x8005                         = 0xFEE8

Ein einziges umgelegtes Flag genügt für eine völlig andere Ausgabe. Ein „knapp daneben“ gibt es bei CRC nicht: Zwei Ergebnisse stimmen entweder überein, oder sie sagen nichts darüber aus, wie weit die Eingaben auseinanderlagen. Deshalb kommen Sie der Ursache auch dann nicht näher, wenn Sie die Abweichung noch so lange anstarren. Die innere Schleife besteht aus nichts als XOR und Schiebeoperationen; der vollständige Leitfaden zu Bit-Operationen behandelt diese Operatoren selbst, und dieser Artikel setzt sie voraus.

3. „CCITT“ ist ein schlechter Name

Der RevEng-CRC-Katalog führt mit Stand September 2026 genau 31 verschiedene 16-Bit-CRC-Definitionen, und drei davon tragen das Etikett CCITT. Keine dieser drei stimmt mit den anderen überein. In der Tabelle unten steht viermal dasselbe Polynom über derselben Eingabe, und viermal kommt etwas anderes heraus:

Gebräuchlicher NameKatalognamePrüfwertinitrefin/refoutxorout
CCITT-FALSECRC-16/IBM-37400x29B10xFFFFfalse0x0000
XMODEMCRC-16/XMODEM0x31C30x0000false0x0000
„das echte“ CCITTCRC-16/KERMIT0x21890x0000true0x0000
CRC-16/MCRF4XX0x6F910xFFFFtrue0x0000

Die Geschichte dahinter ist kurz und wenig hilfreich. Der Katalog führt CRC-16/KERMIT mit den Aliassen CRC-CCITT und CRC-16/CCITT-TRUE, und diese Variante ist gespiegelt. Eine ungespiegelte Implementierung mit init 0xFFFF kursierte ebenfalls weithin unter dem Namen CCITT, weshalb der Katalog „CCITT-FALSE“ als Alias von CRC-16/IBM-3740 verzeichnet. XMODEM liegt dazwischen: gleiches Polynom, keine Spiegelung, init null.

Wenn ein Datenblatt also CCITT sagt, hat es Ihnen mitgeteilt, dass das Polynom 0x1021 lautet, und sonst nichts. Sie haben immer noch vier Kandidaten, und die falsche Wahl liefert Ihnen einen Wert, der genauso plausibel aussieht wie der richtige.

Gewöhnen Sie sich an, die Parameter hinzuschreiben statt eines Namens. poly=0x1021, init=0xFFFF, refin=false, refout=false, xorout=0x0000 in Ihr Protokolldokument zu schreiben, kostet eine Zeile und beseitigt die Mehrdeutigkeit dauerhaft. „CRC-16/CCITT“ zu schreiben leistet das nicht.

4. So finden Sie heraus, welche CRC-16-Variante Sie vor sich haben

Der Katalog löst das mit einem Fingerabdruck. Jeder Eintrag veröffentlicht einen Prüfwert (check value): die CRC über die neun ASCII-Bytes 123456789. Lassen Sie diese Zeichenkette durch die Implementierung laufen, die vor Ihnen liegt, und schlagen Sie das Ergebnis anschließend nach.

VariantePrüfwertpolyinitrefinrefoutxorout
CRC-16/ARC (IBM/LHA)0xBB3D0x80050x0000truetrue0x0000
CRC-16/MODBUS0x4B370x80050xFFFFtruetrue0x0000
CRC-16/USB0xB4C80x80050xFFFFtruetrue0xFFFF
CRC-16/IBM-3740 (CCITT-FALSE)0x29B10x10210xFFFFfalsefalse0x0000
CRC-16/XMODEM0x31C30x10210x0000falsefalse0x0000
CRC-16/KERMIT (das echte CCITT)0x21890x10210x0000truetrue0x0000
CRC-16/GENIBUS0xD64E0x10210xFFFFfalsefalse0xFFFF
CRC-16/MCRF4XX0x6F910x10210xFFFFtruetrue0x0000
CRC-32/ISO-HDLC (Zip, PNG, zlib)0xCBF439260x04C11DB70xFFFFFFFFtruetrue0xFFFFFFFF
CRC-32/BZIP20xFC8919180x04C11DB70xFFFFFFFFfalsefalse0xFFFFFFFF
CRC-32C (Castagnoli, iSCSI)0xE30692830x1EDC6F410xFFFFFFFFtruetrue0xFFFFFFFF
CRC-8/SMBUS0xF40x070x00falsefalse0x00
CRC-8/MAXIM-DOW (1-Wire)0xA10x310x00truetrue0x00

Ein Detail lässt überraschend viele dieser Vergleiche scheitern: 123456789 bedeutet die neun Bytes 31 32 33 34 35 36 37 38 39, nicht die Zahl 123456789 und auch keine nullterminierte Zeichenkette. Übergibt Ihre Sprache der Funktion einen Wide String oder hängt sie ein Terminierungszeichen an, dann hashen Sie eine andere Eingabe, und jede Zeile geht daneben. Nicht-ASCII-Nutzdaten bringen eine zweite Falle mit: Derselbe Text ergibt als UTF-8 und als Windows-1252 zwei verschiedene Bytefolgen und damit zwei verschiedene CRCs. Die ASCII-Tabelle mit Konverter zeigt das Byte zu jedem Zeichen, womit sich diese Frage in zehn Sekunden klären lässt.

Stimmt der Prüfwert mit keiner Zeile überein, schließen Sie zuerst die naheliegenden Ursachen aus, bevor Sie ein eigenes Polynom vermuten:

  1. Byte-Reihenfolge: Möglicherweise lesen Sie das Ergebnis von der Leitung in umgekehrter Reihenfolge.
  2. Erfasste Felder: Der Sender bezieht ein Adressbyte, ein Längenbyte oder das CRC-Feld selbst mit ein oder lässt es weg.
  3. Ein herstellerspezifischer init-Wert, der weder 0x0000 noch 0xFFFF ist. Das kommt in proprietären Zählerprotokollen vor und steht meist in einer Fußnote.

Die Parameter durchprobieren

Wenn der Hersteller es Ihnen nicht sagt und Sie seine Firmware nicht lesen können, bitten Sie stattdessen um eine einzige Sache: einen mitgeschnittenen Frame zusammen mit der CRC, die sein Gerät dafür erzeugt hat. Dann zählen Sie durch. Zwei Polynome, zwei init-Werte, refin und refout unabhängig voneinander, zwei xorout-Werte ergeben 32 Kombinationen, und das ist nichts:

target = 0x4B37                    # der Wert, den ihre Implementierung geliefert hat
for poly in (0x8005, 0x1021):
    for init in (0x0000, 0xFFFF):
        for refin in (False, True):
            for refout in (False, True):
                for xorout in (0x0000, 0xFFFF):
                    if crc(b"123456789", 16, poly, init, refin, refout, xorout) == target:
                        print(f"poly=0x{poly:04X} init=0x{init:04X} "
                              f"refin={refin} refout={refout} xorout=0x{xorout:04X}")
poly=0x8005 init=0xFFFF refin=True refout=True xorout=0x0000

Ein Treffer, und es ist CRC-16/MODBUS. Füttern Sie die Schleife mit einem echten Frame statt mit 123456789, und derselbe 32-fache Durchlauf funktioniert für jede Nachricht, zu der Sie eine als korrekt bekannte CRC besitzen. Überleben mehrere Kombinationen eine Probe, nehmen Sie einen zweiten Frame und bilden die Schnittmenge.

5. Modbus RTU in der Praxis

Modbus RTU verwendet CRC-16/MODBUS: Polynom 0x8005, init 0xFFFF, ein- und ausgangsseitig gespiegelt, kein abschließendes XOR. Für unsere Sechs-Byte-Anfrage lautet die CRC 0xCDC5, und der vollständige Frame sieht so aus:

01 03 00 00 00 0A C5 CD

Die Reihenfolge auf der Leitung ist umgekehrt zur Schreibweise des Werts

Der Wert ist 0xCDC5. Die an den Frame angehängten Bytes sind C5 CD. Modbus schreibt das niederwertige CRC-Byte zuerst vor, also genau die umgekehrte Reihenfolge zu der, in der Sie die Hexadezimalzahl lesen, und das erwischt jedes Mal aufs Neue jemanden. Jedes andere mehrbytige Feld im selben Frame, einschließlich der Registeranzahl 00 0A, ist Big-Endian. Die CRC ist die Ausnahme.

Daraus ergibt sich ein Kniff, der auf der Empfängerseite Arbeit spart. Berechnen Sie CRC-16/MODBUS über den vollständigen Frame, die CRC-Bytes eingeschlossen, und das Ergebnis lautet:

0x0000

Das ist die gesamte Prüfroutine des Empfängers. Es gibt keinen Grund, die letzten beiden Bytes abzutrennen, sie zu vertauschen und mit einem lokal berechneten Wert zu vergleichen. Lassen Sie die CRC über alles laufen, was angekommen ist, und prüfen Sie auf null. Weniger Schritte bedeuten weniger Stellen, an denen sich Bytes falsch herum drehen lassen.

Warum in der Modbus-Dokumentation 0xA001 steht

Öffnen Sie eine beliebige Modbus-Implementierung, und die Konstante im Quelltext lautet fast immer 0xA001, nicht 0x8005. Beide sind richtig. 0xA001 ist 0x8005 mit umgekehrten 16 Bit und gehört zur gespiegelten Form des Algorithmus, bei der das Register nach rechts statt nach links schiebt und die Eingabebytes keine byteweise Umkehrung mehr brauchen. Die beiden Implementierungen erzeugen identische Ausgaben; nur ihr Innenleben unterscheidet sich. 0xA001 im Code zu sehen ist ein zuverlässiges Zeichen dafür, dass Sie eine gespiegelte CRC-16 vor sich haben und kein anderes Polynom.

Wer einen Mitschnitt liest, sollte noch eine zweite Möglichkeit kennen. Modbus ASCII ist ein eigenes Rahmenformat, das jedes Byte als zwei Hexadezimalzeichen zwischen einem führenden 3A (:) und einem abschließenden 0D 0A überträgt und statt einer CRC eine LRC verwendet. Wenn Ihre Aufzeichnung 3A 30 31 30 33 zeigt, wo Sie 01 03 erwartet haben, sind Sie im ASCII-Modus, und keine CRC-Variante wird jemals passen. Die ASCII-Tabelle übersetzt diese Bytes direkt zurück in Zeichen.

Derselbe Algorithmus in C

Modbus-Firmware verwendet das Python-Modell oben so gut wie nie. Sie nutzt direkt die reflektierte Form: nach rechts schieben und mit 0xA001 XOR-verknüpfen.

uint16_t crc16_modbus(const uint8_t *buf, size_t len) {
    uint16_t crc = 0xFFFF;
    for (size_t i = 0; i < len; i++) {
        crc ^= buf[i];
        for (int b = 0; b < 8; b++)
            crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : crc >> 1;
    }
    return crc;
}

Mit dem Frame 01 03 00 00 00 0A liefert diese Funktion 0xCDC5, denselben Wert wie die Python-Engine. Die Prüfzeichenkette 123456789 ergibt 0x4B37. Beides wurde unter Apple clang 21 ausgeführt und stimmt Bit für Bit mit den Tabellen in den Abschnitten 1 und 4 überein.

6. refin/refout ist nicht die Endianness

Beide Begriffe leben auf verschiedenen Ebenen, und weil ein Modbus-Frame beides gleichzeitig enthält, wird die Verwechslung schnell teuer.

Bei der Endianness geht es um Bytes innerhalb eines mehrbytigen Werts: darum, ob 0xCDC5 als CD C5 oder als C5 CD gespeichert und übertragen wird. Innerhalb eines Bytes bewegt sich dabei nichts. Diese Ebene behandelt Big-Endian gegenüber Little-Endian vollständig, und dieser Artikel wiederholt sie nicht.

Bei der Spiegelung geht es dagegen um Bits innerhalb eines einzelnen Bytes. refin kehrt die acht Bits jedes Bytes um, bevor das Register es zu sehen bekommt: Bit 0 wird zu Bit 7. Die Position des Bytes in der Nachricht ändert sich nie. Von der Endianness ist dabei nichts zu sehen, und umgekehrt genauso.

In einem Modbus-Frame passiert beides, unabhängig voneinander:

  • Innerhalb des Algorithmus sind refin und refout wahr, Bits werden also innerhalb der Bytes umgekehrt.
  • Auf der Leitung wird die fertige CRC mit dem niederwertigen Byte zuerst angehängt, was eine Entscheidung des Protokolls über die Byte-Reihenfolge ist.

Wer die Spiegelung abschaltet, um ein Problem mit der Byte-Reihenfolge zu „reparieren“, hat danach eine völlig andere Variante am Hals. Wer umgekehrt die letzten beiden Frame-Bytes vertauscht, um eine falsche Spiegelung auszugleichen, bekommt einen Wert, der auf neue Weise falsch ist. Nehmen Sie sich die Ebenen einzeln vor: erst den Prüfwert mit dem Algorithmus allein in Ordnung bringen, danach den Aufbau des Frames.

7. CRC-32 oder CRC-16: Welche sollten Sie verwenden?

CRC-32 hat dasselbe Problem wie CRC-16, nur weniger sichtbar, weil eine Variante so vollständig dominiert, dass die meisten Entwickler nie erfahren, dass es andere gibt.

VariantePrüfwertpolyrefin/refout
CRC-32/ISO-HDLC0xCBF439260x04C11DB7true
CRC-32/BZIP20xFC8919180x04C11DB7false
CRC-32C (Castagnoli)0xE30692830x1EDC6F41true

ISO-HDLC ist das, was zlib.crc32 berechnet, und im gesamten Web-Stack begegnet es einem ständig. Zip speichert es pro Eintrag im zentralen Verzeichnis. Jeder PNG-Chunk endet mit einem, das Chunk-Typ und Daten abdeckt. Die Frame Check Sequence von Ethernet verwendet denselben Parametersatz am Ende jedes Frames, den Ihr Server verschickt. BZIP2 ist dasselbe Polynom mit abgeschalteten Spiegelungen, was einen Wert erzeugt, der mit ISO-HDLC nichts gemeinsam hat.

CRC-16 taucht in derselben Nachbarschaft ebenfalls auf: Redis Cluster ordnet einen Schlüssel mit CRC16(key) mod 16384 einem seiner 16.384 Slots zu, weshalb Schlüssel mit demselben Hash-Tag auf demselben Knoten landen.

Warum CRC-32C in der Hardware gelandet ist

CRC-32C verwendet ein anderes Polynom mit besserer Fehlererkennung über die kurzen Blöcke, die Speicher- und Netzwerkprotokolle tatsächlich verschicken. Das hat ihm iSCSI, die Metadaten-Prüfsummen von ext4, SCTP und Btrfs eingebracht. Dann hat Intel es in Silizium gegossen: Der SSE4.2-Befehl crc32 berechnet CRC-32C direkt, was daraus auf modernen x86-Prozessoren eine praktisch kostenlose Integritätsprüfung macht. Wenn Sie heute für neuen Code eine CRC auswählen und keine Kompatibilitätsvorgabe haben, dann diese.

Keine davon ist ein Hash zum Überprüfen eines Downloads. Wenn Sie eine Prüfsumme brauchen, die bestätigt, dass eine Datei unversehrt angekommen ist, ist der MD5-Hash-Generator das vertraute Werkzeug, und MD5 gegenüber SHA-256 behandelt, welchem Digest man wofür trauen sollte. Die Trennlinie ist einfach: Eine CRC beantwortet „wurde das hier beschädigt“, ein kryptografischer Hash beantwortet „ist das genau der Inhalt, den ich erwarte“.

8. Eine CRC verhindert keine Manipulation

Die folgenden zwei JSON-Nachrichten bedeuten das Gegenteil voneinander und haben trotzdem dieselbe CRC-32:

message A = b'{"to":"alice","amount":100}  H\xf6\xc0E'
message B = b'{"to":"mallory","amount":999}\xb2\xb2\xc2\xc2'
CRC-32(A) = 0x12345678
CRC-32(B) = 0x12345678

SHA-256 über dieselben beiden:

A -> a83b90f5e3f21dd32039e0e693a8b15864eda83e258c115deeee50a60c09ae23
B -> f9319ee0507d719c7260535dff33df721ddd9c0e1857690f3d8e02f92601b742

Bemerkenswert an dieser Kollision ist weniger, dass es sie gibt, als wie sie zustande kam. Durchprobiert wurde hier nichts; die abschließenden Bytes ergeben sich aus einer algebraischen Auflösung. CRC ist eine lineare Funktion, und das Anhängen von vier Bytes an eine Nachricht bildet sich als Bijektion auf die CRC-32-Ausgabe ab. Zu jeder Nachricht und jedem Zielwert gibt es also genau ein Vier-Byte-Suffix, das Sie dorthin bringt, und es zu finden ist reines Rechnen.

Hängt man die vier so berechneten Bytes 46 C9 6E 0B an hello world an, kommt exakt der Zielwert heraus:

CRC-32(b'hello world' + 46 C9 6E 0B) = 0xDEADBEEF   target 0xDEADBEEF   MATCH

Ein Angreifer, der Ihre Nutzdaten verändern kann, kann folglich auch Ihre CRC nachziehen, in konstanter Zeit und ohne nach irgendetwas suchen zu müssen. Der Nachricht ein Geheimnis voranzustellen rettet sie nicht: Bei einer Änderung, welche die Länge beibehält, hängt die Korrektur an der CRC nur von den geänderten Bits ab und lässt sich damit berechnen, ohne das Geheimnis jemals zu kennen. Das ist die Form des Angriffs, an dem WEP zerbrochen ist.

Eine CRC schützt gegen Übertragungsrauschen, weil Rauschen zufällig ist und kein Ziel verfolgt. Gegen einen Angreifer, der genau umgekehrt vorgeht, hilft sie nicht. Wenn die Anforderung Authentizität statt Integrität lautet, brauchen Sie eine schlüsselbasierte Konstruktion: Der HMAC-Generator erzeugt eine solche über beliebige Nutzdaten und einen Schlüssel, und warum die Prüfung von Webhook-Signaturen fehlschlägt geht die Stellen durch, über die man stolpert, wenn man das an einem echten Endpunkt verdrahtet.

9. FAQ

Ist CRC-16/CCITT dasselbe wie CRC-16/CCITT-FALSE?

Nein, CRC-16/CCITT-FALSE und CRC-16/CCITT sind verschiedene Varianten. CCITT-FALSE ist CRC-16/IBM-3740: init 0xFFFF, ungespiegelt, Prüfwert 0x29B1. Die Variante, die mit einem schlichten CCITT üblicherweise gemeint ist, ist CRC-16/KERMIT: init 0x0000, gespiegelt, Prüfwert 0x2189. Sie teilen sich das Polynom 0x1021 und stimmen in nichts anderem überein.

Warum weicht ein Online-Rechner von meinem Gerät ab?

Wegen unterschiedlicher Parameter, fast immer. Das Werkzeug verwendet standardmäßig eine Variante, das Gerät implementiert eine andere. Lassen Sie die ASCII-Bytes 123456789 durch beide laufen, vergleichen Sie die zwei Prüfwerte mit der Tabelle in Abschnitt 4, und der abweichende Parameter verrät sich meist in weniger als einer Minute.

Warum stellt Modbus das niederwertige CRC-Byte voran?

Weil die Spezifikation es so vorschreibt, und es ist das einzige Feld im Frame, das sich so verhält. Registeradressen und Anzahlen sind Big-Endian, die abschließende CRC ist es nicht. Berechnen Sie CRC-16/MODBUS über den gesamten Frame einschließlich dieser beiden Bytes und prüfen Sie auf 0x0000, statt sie selbst umzusortieren.

Was genau kehren refin und refout um?

Bits innerhalb eines Bytes, niemals Bytes innerhalb einer Nachricht. refin kehrt die acht Bits jedes Eingabebytes vor der Verarbeitung um, refout kehrt das fertige Register vor dem letzten XOR um. Beide sind unabhängig von der Endianness, die regelt, wie ein mehrbytiger Wert auf der Leitung angeordnet wird.

Sollte ich CRC-16 oder CRC-32 verwenden?

CRC-16 übersieht eine zufällige Verfälschung ungefähr einmal in 65.536 Fällen, CRC-32 einmal in 4,3 Milliarden. Für kurze serielle Frames von einigen Dutzend Bytes ist CRC-16 ausreichend und wird vom Protokoll ohnehin meist vorgeschrieben. Für Dateien, Netzwerk-Frames und alles jenseits weniger Kilobyte nehmen Sie CRC-32.

Kann ich eine CRC als API-Signatur verwenden?

Nein, eine CRC kann nicht als API-Signatur dienen. CRC ist linear, wer also die Nutzdaten verändert, kann einen passenden Wert neu berechnen, und das Anhängen von vier gewählten Bytes erreicht jedes CRC-32-Ziel, das Sie nennen. Signaturen brauchen einen geheimen Schlüssel und eine nichtlineare Konstruktion. Verwenden Sie HMAC mit SHA-256.

Lassen sich die ursprünglichen Daten aus einem CRC-Wert wiederherstellen?

Nein, die Originaldaten lassen sich aus einem CRC-Wert nicht wiederherstellen. Eine CRC-16 verdichtet jede Eingabe auf 16 Bit, also teilen sich unendlich viele Nachrichten jeden Wert. Die umgekehrte Richtung ist für Angreifer trotzdem nützlich: Zu einem vorgegebenen Zielwert lässt sich eine Nachricht konstruieren, die genau ihn erzeugt, und exakt deshalb authentifiziert eine CRC gar nichts.

Im Herstellerdokument steht nur „CRC-16“. Wie nagle ich die Parameter fest?

Bitten Sie um einen mitgeschnittenen Frame plus die CRC, die das Gerät dafür berechnet hat, und lassen Sie dann den 32-fachen Durchlauf aus Abschnitt 4 gegen dieses Paar laufen. Überlebt mehr als ein Parametersatz, wiederholen Sie das mit einem zweiten Frame und behalten die Schnittmenge. Zwei Proben sind fast immer entscheidend.

Tags: crc checksum modbus embedded data-integrity

Verwandte Artikel

Alle Artikel anzeigen