Cyclic Redundancy Check at the binary level — how polynomial division using XOR and shift operations detects errors in data transmission and storage.
A CRC (Cyclic Redundancy Check) treats a block of data as a giant binary number and performs polynomial division on it. The remainder of that division is the CRC checksum. What makes CRC fascinating from a bitwise perspective is that the entire division operation is implemented using only XOR and shift — no actual division hardware needed. I wrote my first CRC implementation in C after reading the PNG specification, and watching those 32 XOR-and-shift operations produce the same result as zlib was one of those "aha" moments.
CRC is fundamentally different from a simple checksum. While a XOR checksum is just a parity calculation across all bytes, CRC performs a rigorously defined mathematical operation that guarantees detection of all burst errors up to the polynomial length. CRC-32, used in Ethernet, PNG, ZIP, and countless other formats, detects 100% of burst errors up to 32 bits long.
When debugging a serial communication protocol, I manually computed the CRC of a test packet by tracing through the polynomial division. The exercise reinforced why CRC is preferred over simple checksums for detecting burst errors in noisy communication channels.
CRC operates in what mathematicians call GF(2) — the Galois Field with two elements. In this field, addition and subtraction are the same operation: XOR. There is no carry in addition and no borrow in subtraction. This is why CRC polynomial division is implemented entirely with XOR gates in hardware and XOR operations in software.
The polynomial is the key. Every CRC standard defines a specific polynomial. CRC-32 uses 0x04C11DB7 (which represents x^32 + x^26 + x^23 + ... + 1). The degree of the polynomial determines the width of the CRC — a degree-N polynomial produces an N-bit checksum.
Here is what a raw, bit-by-bit CRC-32 computation looks like. This processes each bit individually and is straightforward to understand even if it is not the fastest approach.
// Bit-by-bit CRC-32 (straightforward but slow)
uint32_t crc32_bitwise(const uint8_t *data, size_t len) {
uint32_t crc = 0xFFFFFFFF; // Initial value (all 1s)
const uint32_t poly = 0xEDB88320; // Reflected CRC-32 polynomial
for (size_t i = 0; i < len; i++) {
crc ^= data[i]; // XOR next byte into the CRC register
for (int bit = 0; bit < 8; bit++) {
if (crc & 1) {
crc = (crc >> 1) ^ poly; // LSB is 1: shift and XOR polynomial
} else {
crc >>= 1; // LSB is 0: just shift
}
}
}
return crc ^ 0xFFFFFFFF; // Final XOR (all 1s)
}
Each byte requires 8 iterations of the inner loop. For a megabyte of data, that is 8 million iterations. This works, but it is not fast enough for network interfaces that run at gigabits per second.
The standard optimization precomputes the CRC result for all 256 possible byte values in a table. Instead of 8 inner-loop iterations per byte, the table method processes one byte per iteration with a single XOR and table lookup. This is how zlib, PNG, and Ethernet implement CRC-32.
// Table-driven CRC-32 (standard optimization)
uint32_t crc32_table[256];
void crc32_init_table(void) {
for (uint32_t i = 0; i < 256; i++) {
uint32_t crc = i;
for (int j = 0; j < 8; j++) {
crc = (crc & 1) ? ((crc >> 1) ^ 0xEDB88320) : (crc >> 1);
}
crc32_table[i] = crc;
}
}
uint32_t crc32_table_driven(const uint8_t *data, size_t len) {
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < len; i++) {
uint8_t index = (crc ^ data[i]) & 0xFF;
crc = (crc >> 8) ^ crc32_table[index];
}
return crc ^ 0xFFFFFFFF;
}
The table approach runs about 8 times faster than the bit-by-bit method. Hardware CRC implementations go even further, processing 32 or 64 bits per clock cycle using specialized XOR trees. Every modern CPU has a CRC instruction (SSE 4.2's CRC32 on x86, or the CRC extension on ARM).
Different standards use different polynomials. Each polynomial gives different error-detection properties. Here are the most widely used ones:
| Name | Width | Polynomial (Hex, Normal) | Used In |
|---|---|---|---|
| CRC-8 | 8 | 0x07 | 1-Wire, Dallas/Maxim sensors |
| CRC-16-IBM | 16 | 0x8005 | MODBUS, USB, PPP |
| CRC-16-CCITT | 16 | 0x1021 | XMODEM, Bluetooth, SD cards |
| CRC-32 | 32 | 0x04C11DB7 | Ethernet, PNG, ZIP, GZip |
| CRC-32C (Castagnoli) | 32 | 0x1EDC6F41 | iSCSI, SCTP, ext4, Google |
| CRC-64-ECMA | 64 | 0x42F0E1EBA9EA3693 | ECMA-182, LTFS |
CRC-32C is worth special mention. It uses a different polynomial that is faster to compute in hardware and has slightly better error-detection properties for long data streams. Google uses CRC-32C extensively in its filesystems and networking stack.
When the receiver computes the CRC of the received data (including the appended CRC), a perfect transmission produces a CRC of zero. Any non-zero result means corruption occurred. Here is what CRC-32 guarantees:
This is dramatically better than simple parity or XOR checksums, which is why CRC is the standard for Ethernet frames, hard drive sectors, and file compression formats.
CRC is designed to detect accidental corruption, not intentional tampering. An attacker who knows the CRC polynomial can trivially modify data and recompute a valid CRC. For security-critical integrity checks, use a cryptographic MAC like HMAC-SHA256 instead.
Use our SHA-256 generator to compute file hashes. While SHA-256 is cryptographically secure, CRC-32 is better suited for quick integrity checks where security is not a concern. Try it with a small file to see how a single byte change alters the checksum.
CRC (Cyclic Redundancy Check) treats data as a binary polynomial and performs polynomial division using XOR as the subtraction operation. The remainder of this division is the CRC checksum. CRC-32 uses a 33-bit divisor polynomial (the 32 bits of the result plus an implied leading 1), and the division is performed bit-by-bit using only XOR and shift operations.
CRC operates in GF(2) (Galois Field of 2 elements), where addition and subtraction are both XOR. In this field, there is no carry — each bit position is independent. This is why polynomial division for CRC is implemented entirely with XOR and shift operations. No borrows or carries mean the hardware implementation is extremely simple and fast.
CRC-32 detects all single-bit errors, all two-bit errors, all odd numbers of bit errors, all burst errors up to 32 bits, and 99.99999998% of longer burst errors. This is far better than simple XOR checksums or parity bits. However, CRC is not cryptographically secure — a CRC can be trivially forged by an attacker who knows the polynomial.
A CRC lookup table precomputes the remainder for all 256 possible byte values. Instead of processing the CRC bit-by-bit (8 shifts per byte), the table approach processes one byte at a time with a single table lookup and three XOR operations. This makes CRC-32 approximately 8 times faster in software, and it is the standard implementation used in zlib, PNG, and Ethernet.
Yes, but only for accidental corruption detection. CRC-32 is excellent for detecting transmission errors, media degradation, or copy mistakes. For intentional tampering detection, use SHA-256 or another cryptographic hash. Many file formats store both CRC-32 (for quick corruption checks) and SHA-256 (for security verification).