CRC-8, CRC-16 and CRC-32 without the guesswork

Published 11 September 2026 · Updated 11 September 2026 · 11 min read

Sine-wave illustration
Bit-level checksums computed in the browser, with every polynomial step shown.

"The CRC does not match" is one of the most common sentences in embedded work, and it almost never means the algorithm is wrong. It means two implementations are using different parameters, different byte order, or a different definition of where the message ends and the checksum begins. There are only five parameters and three conventions to check, and this page is arranged around finding which one you are disagreeing about.

The calculator below implements the standard catalogue model, reproduces every published check value, and shows the CRC both as a number and as the bytes that go on the wire.

CRC calculator

Paste either text or hex bytes. The check row proves the parameters are the catalogue set.

Separators are ignored: spaces, commas, colons, dashes and underscores all work.
CRC
Decimal
Wire bytes, low byte first
Wire bytes, high byte first
Bytes in the message
Check value for "123456789"
Catalogue check value
Parameters

Everything runs in the browser, including the bit-at-a-time loop, so this is a reference implementation you can read rather than a black box. A 256-entry table version produces identical results and is what you would ship in firmware.

What a CRC actually is

A CRC treats the message as one enormous binary polynomial and divides it by a fixed generator polynomial, keeping the remainder. Everything else — the initial value, the reflection, the final XOR — is a convention layered on top to catch particular kinds of error. Division is done modulo 2, where addition and subtraction are both XOR, which is why the implementation is a shift-and-XOR loop and why a CRC is trivially cheap in hardware.

That structure gives CRCs their specific strengths: they catch every burst error up to the width of the polynomial, every error pattern with an odd number of flipped bits (when the polynomial has a factor of x+1, as most catalogue choices do), and all single-bit errors. It gives them one important weakness too: a CRC is a checksum, not a signature. Anyone can compute it, so it detects accidents and not tampering. Use a MAC for the latter.

The five parameters

  • Width — 8, 16 or 32 bits here.
  • Polynomial — written without its leading term, as the catalogue does. CRC-32 is 0x04C11DB7, meaning x32 + x26 + x23 + ... + 1. There is always exactly one more term than the hex constant has bits, and losing track of that is the first way two implementations part company.
  • Init — the register's starting value. It is not cosmetic: with init = 0 a message starting with one or more zero bytes produces the same CRC as the same message without them, so a truncated frame passes. Starting at 0xFFFF or 0xFFFFFFFF removes that blind spot, which is why nearly every protocol uses a non-zero init.
  • Refin — reflect each input byte before it is used. This exists because serial links historically sent the least significant bit first, and a CRC built for MSB-first hardware produces the wrong answer if the bytes arrive the other way round.
  • Refout — reflect the final register before the XOR. It almost always matches refin; when the two disagree you have almost certainly misread a parameter table.
  • Xorout — XOR applied at the end. It guarantees that a zero-length message does not produce a zero CRC, and generally stops the all-zero codeword from being a valid message.

The check value: one test that settles everything

The universal check vector is the nine ASCII bytes "123456789" — not followed by a newline, not in quotes. Every parameter set in the catalogue has a published CRC for it, and if your implementation reproduces that number then the polynomial, the init, both reflections and the final XOR are all correct. There is nothing else to test.

Parameter setPolyInitRef in/outXoroutCheck
CRC-80x070x00no / no0x000xF4
CRC-8/SAE-J18500x1D0xFFno / no0xFF0x4B
CRC-8/MAXIM-DOW0x310x00yes / yes0x000xA1
CRC-16/ARC0x80050x0000yes / yes0x00000xBB3D
CRC-16/MODBUS0x80050xFFFFyes / yes0x00000x4B37
CRC-16/USB0x80050xFFFFyes / yes0xFFFF0xB4C8
CRC-16/CCITT-FALSE0x10210xFFFFno / no0x00000x29B1
CRC-16/XMODEM0x10210x0000no / no0x00000x31C3
CRC-16/KERMIT0x10210x0000yes / yes0x00000x2189
CRC-16/X-250x10210xFFFFyes / yes0xFFFF0x906E
CRC-32 (zlib, PKZIP)0x04C11DB70xFFFFFFFFyes / yes0xFFFFFFFF0xCBF43926
CRC-32C (Castagnoli)0x1EDC6F410xFFFFFFFFyes / yes0xFFFFFFFF0xE3069283
CRC-32/BZIP20x04C11DB70xFFFFFFFFno / no0xFFFFFFFF0xFC891918
CRC-32/MPEG-20x04C11DB70xFFFFFFFFno / no0x000000000x0376E6E7
CRC-32/POSIX (cksum)0x04C11DB70x00000000no / no0xFFFFFFFF0x765E7680
CRC-32/JAMCRC0x04C11DB70xFFFFFFFFyes / yes0x000000000x340BC6D9

Two relationships in that table are worth knowing because they let you check a parameter set without computing anything. CRC-32 and CRC-32/JAMCRC differ only in the final XOR, so for any input one is the bitwise complement of the other — with the test vector, 0xCBF43926 and 0x340BC6D9 XOR to 0xFFFFFFFF. And MODBUS and USB differ the same way, so their 16-bit results also XOR to 0xFFFF. If your implementation gives two values that are not complements, the final XOR is not the thing that is wrong.

Wire order: the third disagreement

Once both ends agree on the five parameters, the remaining failure is byte order. A CRC-16 value of 0x4B37 goes out as 37 4B in Modbus RTU, which transmits the low byte first; the same value in a big-endian protocol would be 4B 37. Both are the same CRC and neither is wrong — the frame format decides. The calculator reports both orders so you can match the capture in front of you instead of trying to remember.

A related convention worth stating explicitly in any implementation: does the transmitted checksum itself get included in the calculation? It must not — the CRC is computed over the message as it will be transmitted with the checksum field excluded (or zeroed), and a surprisingly large share of "one byte off" bugs come from computing it over a buffer that already contains a previous CRC.

A parameter reference that resolves the common cases

  • Modbus RTU — CRC-16/MODBUS, appended low byte first.
  • Ethernet, PNG, gzip, zip — CRC-32 (ISO-HDLC), appended as four bytes, little-endian in PNG chunks and in the zip local header.
  • iSCSI, ext4, some SSDs — CRC-32C, which hardware accelerates through a single instruction.
  • MPEG-2 transport streams — CRC-32/MPEG-2, never reflected.
  • 1-Wire devices — CRC-8/MAXIM-DOW, the reflected 8-bit case.
  • XMODEM and friends — CRC-16/XMODEM, init zero.

Five failure modes worth checking in order

  1. Wrong check value. Run the test vector first, every time. If it fails, the parameters are wrong and nothing else matters.
  2. Reflected versus unreflected. If your CRC is a plausible-looking wrong number, this is the first suspect — and the bit-reversed output is a strong hint that only refout is missing.
  3. Byte order on the wire. The value is right; the two bytes are swapped.
  4. The checksum inside its own calculation. A one-byte or four-byte discrepancy that shifts when the message length changes.
  5. Extra bytes in the message. A trailing newline, a length field, a start byte, or a padding byte that one end includes and the other does not.

Summary

Fix the five parameters against the check vector, decide the wire order from the protocol rather than from habit, and exclude the checksum field from its own calculation. Almost every CRC bug is one of those three.

Values shown are engineering aids rather than measurements; see the disclaimer and the tool index.