Variantes CRC-16: por qué MODBUS, CCITT y XMODEM difieren
«CRC-16» nombra a una familia entera de algoritmos cuyos miembros no se ponen de acuerdo entre sí. Entrega los mismos bytes a MODBUS, a CCITT-FALSE y a XMODEM y recibirás tres números de 16 bits sin ninguna relación entre ellos.
Cinco constantes deciden qué miembro estás ejecutando: poly, init, refin, refout y xorout. Cambia una sola y la salida cambia por completo, sin ningún parecido parcial que sirva de aviso. Ahí está toda la respuesta a «¿por qué mi CRC no coincide con el del dispositivo?»: el dispositivo y tú corren variantes distintas, y ninguna de las dos partes dejó escrito cuál.
Lo demás son detalles, que es donde se va la tarde: qué hace cada parámetro, cómo averiguar la variante que tienes delante cuando nadie la documentó, el orden de bytes que Modbus RTU pone en el cable y por qué un CRC frena el ruido de línea pero no a un atacante.
Cada valor de esta guía salió de un modelo CRC parametrizado ejecutado sobre Python 3.14.7 en macOS, verificado de cuatro maneras: contra
zlib.crc32ybinascii.crc_hqxde la biblioteca estándar, contra los valores check publicados en el catálogo CRC de RevEng, y contra la documentación del fabricante de cada juego de parámetros.
1. La misma trama, cuatro resultados CRC-16
Esta es una petición Modbus RTU real. Esclavo 01, código de función 03 (leer registros holding), dirección inicial 0x0000, cantidad 0x000A:
01 03 00 00 00 0A
Con esos seis bytes, cuatro variantes habituales dan cuatro respuestas distintas:
| Variante | Resultado | En el cable (byte bajo primero) |
|---|---|---|
| CRC-16/MODBUS | 0xCDC5 | C5 CD |
| CRC-16/IBM-3740 (CCITT-FALSE) | 0x0428 | 28 04 |
| CRC-16/XMODEM | 0x0A38 | 38 0A |
| CRC-16/ARC | 0xD6C5 | C5 D6 |
Nada conecta esos cuatro valores: no se diferencian en una constante, y ningún truco de ordenamiento convierte uno en otro. Que dos empiecen por el mismo byte en el cable es casualidad.
La consecuencia práctica: la cadena «CRC-16» en una hoja de datos casi no aporta información. Te dice que la salida mide 16 bits de ancho y ahí se acaba. Si quieres mirar un resultado en otra base mientras lo comparas con un dispositivo que imprime binario, el conversor de bases numéricas pasa un valor de 16 bits entre hexadecimal y binario sin que tengas que pensar en el relleno.
2. Cinco parámetros lo deciden todo
Debajo de todas las variantes está la misma máquina: un registro de desplazamiento que se come un bit por vez y hace XOR con una constante cada vez que un 1 se cae por arriba. Lo que cambian los parámetros es qué entra al registro, en qué dirección viajan los bits y qué se le hace al resultado al final.
Aquí está el motor completo en catorce líneas de Python. Es una implementación bit a bit, lenta pero legible, y reproduce todos los valores de este artículo:
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
La salida del motor se puede contrastar con la biblioteca estándar de Python. zlib.crc32 y binascii.crc_hqx dan cada uno una respuesta independiente, y las tres coinciden:
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: dos bandos, 0x8005 y 0x1021
El polinomio es la constante que se vuelve a meter en el registro con XOR. Se escribe en forma «normal», con el bit superior implícito, de modo que 0x8005 significa x^16 + x^15 + x^2 + 1 y 0x1021 significa x^16 + x^12 + x^5 + 1.
El bucle de desplazamiento y XOR es una división polinómica larga sobre GF(2). Cada bit del mensaje es un paso de la división, el polinomio es el divisor y el registro guarda el resto en curso.
Casi todos los CRC-16 que te vas a encontrar usan uno de esos dos. 0x8005 cubre ARC, MODBUS y USB. 0x1021 cubre todo el enredo CCITT más XMODEM, KERMIT y las variantes de RFID. Por eso el polinomio por sí solo nunca identifica una variante.
init: el valor inicial del registro
Dominan dos valores, 0x0000 y 0xFFFF. La diferencia importa más de lo que parece. Empieza en cero y un byte cero deja el registro en cero, con lo que 00 00 01 y 01 producen CRC idénticos. Un mensaje que gana o pierde ceros a la izquierda durante el transporte pasa la comprobación. Arrancar en 0xFFFF elimina ese punto ciego. Modbus, CCITT-FALSE y USB lo hacen por eso.
refin y refout: orden de bits, no de bytes
refin invierte los ocho bits de cada byte de entrada antes de que llegue al registro. refout invierte el registro final antes del último XOR. El hardware que saca los datos con el bit menos significativo primero obtiene esas reflexiones gratis; las variantes reflejadas casi siempre nacieron en protocolos serie.
Este es el par de parámetros que más se confunde con el endianness, que es otra cosa y vive en otro nivel. La sección 6 los separa.
xorout: el XOR final
El último paso, aplicado al registro después de refout. Normalmente 0x0000 o 0xFFFF en CRC-16, y 0xFFFFFFFF en toda la familia CRC-32. Es el parámetro que más fácilmente se pasa por alto, porque un desajuste aquí se ve exactamente igual que un desajuste en cualquier otro sitio.
width: 8, 16 o 32 bits
El ancho fija el techo de detección. Un CRC de ancho n atrapa cualquier ráfaga de error de hasta n bits, y se le escapa una corrupción aleatoria con probabilidad de aproximadamente 2^-n. Eso es 1 entre 65,536 para CRC-16 y 1 entre 4.3 mil millones para CRC-32.
Partiendo de CRC-16/XMODEM y cambiando exactamente un parámetro cada vez, contra la entrada de prueba estándar 123456789, sale esto:
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
Cambia una bandera y la salida no se parece en nada a la anterior. El CRC no tiene noción de «cerca»: dos resultados coinciden o no coinciden, y la distancia entre ellos no dice nada sobre lo lejos que estaban las entradas. Quedarse mirando un desajuste nunca localiza la causa. El bucle interno no es más que XOR y desplazamientos; la guía completa de operaciones bit a bit cubre esos operadores en sí, y este artículo los da por sabidos.
3. «CCITT» es un mal nombre
El catálogo CRC de RevEng recoge, a fecha de septiembre de 2026, 31 definiciones distintas de CRC de 16 bits, y tres de ellas llevan la etiqueta CCITT. Esas tres no coinciden entre sí. Las cuatro filas de abajo comparten polinomio y entrada, y aun así dan cuatro resultados sin relación:
| Nombre habitual | Nombre en el catálogo | check | init | refin/refout | xorout |
|---|---|---|---|---|---|
| CCITT-FALSE | CRC-16/IBM-3740 | 0x29B1 | 0xFFFF | false | 0x0000 |
| XMODEM | CRC-16/XMODEM | 0x31C3 | 0x0000 | false | 0x0000 |
| la CCITT «de verdad» | CRC-16/KERMIT | 0x2189 | 0x0000 | true | 0x0000 |
| — | CRC-16/MCRF4XX | 0x6F91 | 0xFFFF | true | 0x0000 |
La historia es corta y poco útil. El catálogo lista CRC-16/KERMIT con los alias CRC-CCITT y CRC-16/CCITT-TRUE, y esa es la reflejada. Una implementación sin reflexión con init 0xFFFF también circuló mucho bajo el nombre CCITT; el catálogo la registra como CRC-16/IBM-3740 y le deja «CCITT-FALSE» de alias. XMODEM queda entre medias: mismo polinomio, sin reflexión, init cero.
Así que cuando una hoja de datos dice CCITT, te ha dicho que el polinomio es 0x1021 y nada más. Todavía te quedan cuatro candidatas, y elegir mal te da un valor que se ve igual de plausible que el correcto.
De ahí sale una costumbre que ahorra discusiones: cita los parámetros en vez del nombre. Escribir poly=0x1021, init=0xFFFF, refin=false, refout=false, xorout=0x0000 en tu documento de protocolo ocupa una línea y elimina la ambigüedad para siempre. Escribir «CRC-16/CCITT» deja la discusión abierta.
4. Cómo identificar qué variante de CRC-16 tienes entre manos
El catálogo resuelve esto con una huella digital. Cada entrada publica un valor check: el CRC de los nueve bytes ASCII 123456789. Pasa esa cadena por la implementación que tienes delante y luego busca el resultado.
| Variante | check | poly | init | refin | refout | xorout |
|---|---|---|---|---|---|---|
| CRC-16/ARC (IBM/LHA) | 0xBB3D | 0x8005 | 0x0000 | true | true | 0x0000 |
| CRC-16/MODBUS | 0x4B37 | 0x8005 | 0xFFFF | true | true | 0x0000 |
| CRC-16/USB | 0xB4C8 | 0x8005 | 0xFFFF | true | true | 0xFFFF |
| CRC-16/IBM-3740 (CCITT-FALSE) | 0x29B1 | 0x1021 | 0xFFFF | false | false | 0x0000 |
| CRC-16/XMODEM | 0x31C3 | 0x1021 | 0x0000 | false | false | 0x0000 |
| CRC-16/KERMIT (la CCITT de verdad) | 0x2189 | 0x1021 | 0x0000 | true | true | 0x0000 |
| CRC-16/GENIBUS | 0xD64E | 0x1021 | 0xFFFF | false | false | 0xFFFF |
| CRC-16/MCRF4XX | 0x6F91 | 0x1021 | 0xFFFF | true | true | 0x0000 |
| CRC-32/ISO-HDLC (zip, PNG, zlib) | 0xCBF43926 | 0x04C11DB7 | 0xFFFFFFFF | true | true | 0xFFFFFFFF |
| CRC-32/BZIP2 | 0xFC891918 | 0x04C11DB7 | 0xFFFFFFFF | false | false | 0xFFFFFFFF |
| CRC-32C (Castagnoli, iSCSI) | 0xE3069283 | 0x1EDC6F41 | 0xFFFFFFFF | true | true | 0xFFFFFFFF |
| CRC-8/SMBUS | 0xF4 | 0x07 | 0x00 | false | false | 0x00 |
| CRC-8/MAXIM-DOW (1-Wire) | 0xA1 | 0x31 | 0x00 | true | true | 0x00 |
Hay un detalle que arruina buena parte de estas comparaciones: 123456789 significa los nueve bytes 31 32 33 34 35 36 37 38 39, no el número 123456789 ni una cadena terminada en nulo. Si tu lenguaje le pasa a la función una cadena ancha o le añade un terminador, estás calculando sobre una entrada distinta y ninguna fila va a coincidir. Las cargas útiles no ASCII añaden una segunda trampa: el mismo texto codificado en UTF-8 y en Windows-1252 son dos secuencias de bytes distintas y, por tanto, dos CRC distintos. La tabla ASCII y conversor muestra el byte de cada carácter, y comprobarlo lleva diez segundos.
Si el valor de comprobación no coincide con ninguna fila, descarta primero las causas aburridas antes de sospechar de un polinomio propio:
- Orden de bytes: puede que estés leyendo el resultado al revés desde el cable.
- Campos cubiertos: el emisor incluye o excluye un byte de dirección, un byte de longitud o el propio campo CRC.
- Un
initdel fabricante que no es ni0x0000ni0xFFFF; ocurre en protocolos propietarios de medidores y suele estar enterrado en una nota al pie.
Fuerza bruta sobre los parámetros
Cuando el fabricante no te lo dice y no puedes leer su firmware, pídele una sola cosa: una trama capturada junto con el CRC que su dispositivo produjo para ella. Después, enumera. Dos polinomios, dos valores de init, refin y refout independientes y dos valores de xorout dan 32 combinaciones, que no es nada:
target = 0x4B37 # el valor que devolvió su implementación
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
Un solo acierto, y es CRC-16/MODBUS. Dale una trama real en lugar de 123456789 y el mismo barrido de 32 vías funciona con cualquier mensaje del que tengas un CRC conocido como bueno. Si sobreviven varias combinaciones con una muestra, pasa una segunda trama e intersecta los resultados.
5. Modbus RTU en la práctica
Modbus RTU usa CRC-16/MODBUS: polinomio 0x8005, init 0xFFFF, reflejado a la entrada y a la salida, sin XOR final. Para nuestra petición de seis bytes el CRC es 0xCDC5, y la trama completa queda así:
01 03 00 00 00 0A C5 CD
El orden en el cable es el inverso de cómo escribes el valor
El valor es 0xCDC5. Los bytes que se añaden a la trama son C5 CD. Modbus especifica el byte bajo del CRC primero, que es el orden contrario al que lees el número hexadecimal, y es la trampa en la que cae todo el mundo. Cualquier otro campo multibyte de la misma trama, incluido ese contador de registros 00 0A, es big-endian. El CRC es la excepción.
Eso tiene una consecuencia útil. Calcula CRC-16/MODBUS sobre la trama completa, bytes de CRC incluidos, y el resultado es:
0x0000
Esa es toda la rutina de validación del receptor. No hace falta separar los dos bytes finales, intercambiarlos y compararlos contra un valor calculado localmente. Pasa el CRC sobre todo lo que llegó y comprueba que dé cero. Menos pasos significa menos sitios donde invertir los bytes mal.
Por qué la documentación de Modbus muestra 0xA001
Abre casi cualquier implementación de Modbus y la constante que hay en el código fuente es 0xA001, no 0x8005. Las dos son correctas. 0xA001 es 0x8005 con sus 16 bits invertidos, y pertenece a la forma reflejada del algoritmo, donde el registro se desplaza a la derecha en lugar de a la izquierda y los bytes de entrada no necesitan inversión byte a byte. Las dos implementaciones producen una salida idéntica; solo difieren por dentro. Ver 0xA001 en el código es una señal fiable de que estás mirando un CRC-16 reflejado, no un polinomio distinto.
Un apunte para quien esté leyendo una captura. Modbus ASCII es un entramado aparte que lleva cada byte como dos caracteres hexadecimales entre un 3A (:) inicial y un 0D 0A final, y usa un LRC en lugar de un CRC. Si tu traza muestra 3A 30 31 30 33 donde esperabas 01 03, estás en modo ASCII y ninguna variante de CRC va a coincidir jamás. La tabla ASCII traduce esos bytes de vuelta a caracteres.
El mismo algoritmo en C
El firmware Modbus casi nunca usa el modelo de Python de arriba. Emplea directamente la forma reflejada: desplazar a la derecha y aplicar XOR con 0xA001.
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;
}
Con la trama 01 03 00 00 00 0A, esta función devuelve 0xCDC5, el mismo valor que produjo el motor de Python. La cadena de comprobación 123456789 da 0x4B37. Ambos se ejecutaron con Apple clang 21 y coinciden bit a bit con la tabla de la sección 4.
6. refin/refout no es endianness
Estos dos viven en niveles distintos y la confusión sale cara, porque Modbus contiene ambos a la vez.
El endianness trata de los bytes dentro de un valor multibyte: si 0xCDC5 se guarda o se transmite como CD C5 o como C5 CD. Nada se mueve dentro de un byte. Ese es el nivel que cubre a fondo big-endian frente a little-endian, y este artículo no lo va a repetir.
La reflexión trata de los bits dentro de un solo byte. refin invierte los ocho bits de cada byte antes de que el registro lo vea: el bit 0 pasa a ser el bit 7. La posición del byte dentro del mensaje no cambia nunca. El endianness no puede ver esto, y la reflexión no puede ver el endianness.
En una trama Modbus están ocurriendo las dos cosas, de forma independiente:
- Dentro del algoritmo,
refinyrefoutson true: los bits se invierten dentro de cada byte. - En el cable, el CRC terminado se añade con el byte bajo primero, que es una decisión de orden de bytes tomada por el protocolo.
Apagar la reflexión para «arreglar» un problema de orden de bytes produce una variante completamente distinta, e intercambiar los bytes finales de la trama para compensar un desajuste de reflexión produce un valor que está mal de una manera nueva. Diagnostica de uno en uno: primero acierta el valor check con el algoritmo solo, y después ocúpate del formato de la trama.
7. CRC-32 o CRC-16: ¿cuál deberías usar?
CRC-32 tiene el mismo problema que CRC-16, solo que menos a la vista, porque una variante domina de forma tan aplastante que la mayoría de desarrolladores nunca llega a enterarse de que hay otras.
| Variante | check | poly | refin/refout |
|---|---|---|---|
| CRC-32/ISO-HDLC | 0xCBF43926 | 0x04C11DB7 | true |
| CRC-32/BZIP2 | 0xFC891918 | 0x04C11DB7 | false |
| CRC-32C (Castagnoli) | 0xE3069283 | 0x1EDC6F41 | true |
ISO-HDLC es lo que calcula zlib.crc32, y está por todas partes en el lado web de la pila. Zip lo guarda por cada entrada en el directorio central. Cada chunk de un PNG termina con uno que cubre el tipo de chunk y los datos. La secuencia de verificación de trama de Ethernet usa el mismo juego de parámetros al final de cada trama que envía tu servidor. BZIP2 es el mismo polinomio con las reflexiones apagadas, lo que produce un valor que no comparte nada con ISO-HDLC.
CRC-16 también aparece por el mismo barrio: Redis Cluster enruta una clave hacia uno de sus 16,384 slots con CRC16(key) mod 16384, que es lo que hace que las claves con la misma hash tag caigan en el mismo nodo.
Por qué CRC-32C se ganó la lotería del hardware
CRC-32C usa un polinomio distinto con mejor detección de errores sobre los bloques cortos que los protocolos de almacenamiento y de red envían en la realidad. Eso le valió iSCSI, los checksums de metadatos de ext4, SCTP y Btrfs. Después Intel lo metió en silicio: la instrucción crc32 de SSE4.2 calcula CRC-32C directamente, lo que lo convierte en una comprobación de integridad prácticamente gratis en x86 moderno. Si estás eligiendo un CRC para código nuevo hoy y no tienes ninguna restricción de compatibilidad, esta es la opción.
Ninguno de estos es un hash para verificar una descarga. Cuando lo que quieres es un checksum que confirme que un archivo llegó intacto, el generador de hash MD5 es la herramienta de siempre, y MD5 vs SHA-256 explica en qué digest confiar para cada caso. Un CRC responde «¿esto se corrompió?»; un hash criptográfico responde «¿esto es exactamente el contenido que espero?».
8. Un CRC no detiene la manipulación
Aquí van dos mensajes JSON con significados opuestos y el mismo 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 sobre esos mismos dos:
A -> a83b90f5e3f21dd32039e0e693a8b15864eda83e258c115deeee50a60c09ae23
B -> f9319ee0507d719c7260535dff33df721ddd9c0e1857690f3d8e02f92601b742
La colisión por sí sola no dice gran cosa; interesa cómo se obtuvo. Nadie hizo fuerza bruta aquí: los cuatro bytes finales se despejaron algebraicamente. El CRC es una función lineal, y añadir cuatro bytes a un mensaje se proyecta sobre la salida de CRC-32 como una biyección, de manera que para cualquier mensaje y cualquier valor objetivo existe exactamente un sufijo de cuatro bytes que te lleva hasta ahí. Encontrarlo es aritmética.
La demostración, añadiendo los cuatro bytes despejados 46 C9 6E 0B a hello world:
CRC-32(b'hello world' + 46 C9 6E 0B) = 0xDEADBEEF target 0xDEADBEEF MATCH
Un atacante capaz de modificar tu payload puede entonces arreglar también tu CRC, en tiempo constante, sin necesidad de buscar nada. Poner un secreto al principio del mensaje no lo salva: para una edición que mantiene la longitud, la corrección que se aplica al CRC depende solo de los bits que cambiaron, y se puede calcular sin llegar a conocer el secreto. Esa es la forma del ataque que rompió WEP.
El CRC defiende contra el ruido de transmisión, que es aleatorio y no persigue nada. Un adversario sí persigue algo, y contra él no sirve. Cuando el requisito es autenticidad y no integridad, necesitas una construcción con clave: el generador HMAC produce una sobre cualquier payload y clave, y por qué falla la verificación de firma de webhook recorre las partes que hacen tropezar a la gente al conectarlo a un endpoint real.
9. Preguntas frecuentes
¿Es CRC-16/CCITT lo mismo que CRC-16/CCITT-FALSE?
No, CRC-16/CCITT-FALSE y CRC-16/CCITT son variantes distintas. CCITT-FALSE es CRC-16/IBM-3740: init 0xFFFF, sin reflexión, valor check 0x29B1. La variante que se suele querer decir con CCITT a secas es CRC-16/KERMIT: init 0x0000, reflejada, check 0x2189. Comparten el polinomio 0x1021 y no coinciden en nada más.
¿Por qué una calculadora en línea no coincide con mi dispositivo?
Por parámetros distintos, casi siempre. La herramienta toma una variante por defecto y el dispositivo implementa otra. Pasa los bytes ASCII 123456789 por ambos, compara los dos valores check con la tabla de la sección 4, y el parámetro desajustado suele delatarse en menos de un minuto.
¿Por qué Modbus pone el byte bajo del CRC primero?
Porque la especificación lo dice, y es el único campo de la trama que se comporta así. Las direcciones y los contadores de registros son big-endian; el CRC final no. Calcula CRC-16/MODBUS sobre la trama entera incluyendo esos dos bytes y comprueba que dé 0x0000, en vez de reordenarlos tú.
¿Qué invierten exactamente refin y refout?
Bits dentro de un byte, nunca bytes dentro de un mensaje. refin invierte los ocho bits de cada byte de entrada antes de procesarlo; refout invierte el registro final antes del último XOR. Los dos son independientes del endianness, que rige cómo se coloca un valor multibyte en el cable.
¿Debo usar CRC-16 o CRC-32?
A CRC-16 se le escapa una corrupción aleatoria más o menos una vez de cada 65,536; a CRC-32, una de cada 4.3 mil millones. Para tramas serie cortas de unas pocas decenas de bytes, CRC-16 va bien y además suele venir impuesto por el protocolo. Para archivos, tramas de red y cualquier cosa que pase de unos pocos kilobytes, usa CRC-32.
¿Puedo usar un CRC como firma de API?
No, un CRC no puede servir como firma de API. El CRC es lineal: cualquiera que altere el payload puede recalcular un valor que coincida, y añadiendo cuatro bytes elegidos se llega a cualquier objetivo CRC-32 que quieras nombrar. Las firmas necesitan una clave secreta y una construcción no lineal. Usa HMAC con SHA-256.
¿Se pueden recuperar los datos originales a partir de un valor CRC?
No, los datos originales no se pueden recuperar a partir de un valor CRC. Un CRC-16 comprime cualquier entrada a 16 bits, y por eso infinitos mensajes comparten cada valor. Aun así, la dirección inversa le sigue sirviendo a un atacante: dado un valor objetivo puedes construir un mensaje que lo produzca, que es justo el motivo por el que un CRC no autentica nada.
El documento del fabricante solo dice «CRC-16». ¿Cómo determino los parámetros?
Pide una trama capturada más el CRC que su dispositivo calculó para ella, y luego pasa el barrido de 32 combinaciones de la sección 4 contra ese par. Si sobrevive más de un juego de parámetros, repite con una segunda trama y quédate con la intersección. Dos muestras casi siempre son decisivas.