Variantes CRC-16: por que MODBUS, CCITT e XMODEM diferem
CRC-16 não é um algoritmo. É uma família, e seus membros discordam entre si. Entregue os mesmos bytes ao MODBUS, ao CCITT-FALSE e ao XMODEM e você recebe de volta três números de 16 bits sem nenhuma relação.
Cinco constantes decidem qual membro você está rodando: poly, init, refin, refout e xorout. Mude uma delas e a saída muda por completo, sem nenhuma semelhança parcial para servir de aviso. Essa é a resposta inteira para “por que meu CRC não bate com o do dispositivo”: você e o dispositivo estão rodando variantes diferentes, e nenhum dos dois lados anotou qual.
O resto é detalhe, e o detalhe custa uma tarde quando você erra: o que cada parâmetro faz, como descobrir qual variante está do outro lado do cabo, em que ordem o Modbus RTU joga os dois bytes no fio, e por que um CRC segura ruído de linha e não segura um atacante.
Todos os valores deste guia saíram de um modelo CRC parametrizado rodado em Python 3.14.7 no macOS, conferidos de quatro maneiras: contra
zlib.crc32ebinascii.crc_hqxda biblioteca padrão, contra os valores de verificação publicados no catálogo RevEng CRC e contra a documentação dos fabricantes de cada conjunto de parâmetros.
1. O mesmo quadro, quatro resultados CRC-16
Esta é uma requisição Modbus RTU real. Escravo 01, código de função 03 (leitura de registradores holding), endereço inicial 0x0000, quantidade 0x000A:
01 03 00 00 00 0A
Seis bytes, e quatro variantes devolvem quatro respostas:
| Variante | Resultado | No fio (byte baixo primeiro) |
|---|---|---|
| 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 liga esses quatro valores. Não compartilham padrão de nibbles nem distância constante, e nenhum rearranjo de bytes converte um no outro. Dois deles começam com o mesmo byte no fio, o que é coincidência e não parentesco.
A consequência prática: a expressão “CRC-16” numa folha de dados quase não carrega informação. Ela diz que a saída tem 16 bits de largura e para por aí. Se você quiser olhar um resultado em outra base enquanto compara com um dispositivo que imprime em binário, o conversor de bases numéricas alterna um valor de 16 bits entre hexadecimal e binário sem que você precise pensar em preenchimento.
2. Cinco parâmetros decidem tudo
Por baixo de toda variante existe a mesma máquina: um registrador de deslocamento que engole um bit por vez e faz XOR com uma constante sempre que um 1 cai pelo topo. Os parâmetros decidem o que entra no registrador, em que direção os bits viajam, e o que ainda acontece com o resultado depois que o último byte já passou.
O motor inteiro cabe em catorze linhas de Python. É uma implementação bit a bit, lenta mas legível, e reproduz todos os valores deste artigo:
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
A saída do motor pode ser conferida contra a biblioteca padrão do Python. zlib.crc32 e binascii.crc_hqx dão cada um uma resposta independente, e as três batem:
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: dois campos, 0x8005 e 0x1021
O polinômio é a constante que volta ao registrador por XOR. Ele é escrito na forma “normal”, com o bit mais alto implícito, então 0x8005 significa x^16 + x^15 + x^2 + 1 e 0x1021 significa x^16 + x^12 + x^5 + 1.
Quase todo CRC-16 que você encontra usa um desses dois. 0x8005 cobre ARC, MODBUS e USB. 0x1021 cobre todo o emaranhado CCITT mais XMODEM, KERMIT e as variantes de RFID. É por isso que o polinômio sozinho nunca identifica uma variante.
O laço de deslocamento e XOR é uma divisão polinomial longa sobre GF(2). Cada bit da mensagem é um passo da divisão, o polinômio é o divisor e o registrador guarda o resto corrente.
init: o valor inicial do registrador
Dois valores dominam, 0x0000 e 0xFFFF. A diferença pesa mais do que parece. Comece em zero e um byte zero deixa o registrador em zero, de modo que 00 00 01 e 01 produzem CRCs idênticos. Uma mensagem que ganha ou perde zeros à esquerda no caminho passa na verificação. Começar em 0xFFFF elimina esse ponto cego, e é por isso que Modbus, CCITT-FALSE e USB usam esse valor.
refin e refout: ordem dos bits, não ordem dos bytes
refin inverte os oito bits de cada byte de entrada antes que ele chegue ao registrador. refout inverte o registrador final antes do último XOR. Hardware que desloca os dados para fora com o bit menos significativo primeiro ganha essas reflexões de graça, e por isso as variantes refletidas costumam ser as que nasceram em protocolos seriais.
Esse é o par de parâmetros que mais se confunde com endianness. É outra coisa, em outro nível, e a seção 6 separa os dois.
xorout: o XOR final
O último passo, aplicado ao registrador depois de refout. Normalmente 0x0000 ou 0xFFFF no CRC-16, e 0xFFFFFFFF em toda a família CRC-32. É o parâmetro mais barato de errar, porque uma divergência aqui parece exatamente igual a uma divergência em qualquer outro ponto.
width: 8, 16 ou 32 bits
A largura define o teto de detecção. Um CRC de largura n pega todo erro em rajada de até n bits e deixa passar uma corrupção aleatória com probabilidade em torno de 2^-n. Isso dá 1 em 65.536 para o CRC-16 e 1 em 4,3 bilhões para o CRC-32.
Partindo do CRC-16/XMODEM e mudando exatamente um parâmetro por vez, contra a entrada de sondagem padrão 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
Um sinalizador invertido e a saída já não tem nada a ver com a anterior. CRC não tem noção de “perto”. Dois resultados ou batem, ou não carregam informação nenhuma sobre quão distantes estavam as entradas, e é por isso que ficar encarando uma divergência nunca localiza a causa. O laço interno é só XOR e deslocamentos; o guia completo de operações bit a bit cobre esses operadores em si, e este artigo já os pressupõe.
3. “CCITT” é um nome ruim
O catálogo CRC do RevEng lista, em setembro de 2026, 31 definições distintas de CRC de 16 bits, e três delas carregam o rótulo CCITT. Essas três já foram chamadas de CCITT por aí, e nenhuma delas concorda com as outras. Olhe as quatro linhas abaixo. Mesmo polinômio, mesma entrada, quatro resultados sem relação:
| Nome comum | Nome no 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 |
| o CCITT “de verdade” | CRC-16/KERMIT | 0x2189 | 0x0000 | true | 0x0000 |
| — | CRC-16/MCRF4XX | 0x6F91 | 0xFFFF | true | 0x0000 |
A história é curta e não ajuda. O catálogo lista o CRC-16/KERMIT com os apelidos CRC-CCITT e CRC-16/CCITT-TRUE, e essa variante é refletida. Uma implementação não refletida com init 0xFFFF também circulou bastante sob o nome CCITT, e por isso o catálogo registra “CCITT-FALSE” como apelido do CRC-16/IBM-3740. O XMODEM fica no meio dos dois: mesmo polinômio, sem reflexão, init zero.
Ou seja, quando uma folha de dados diz CCITT, ela te informou que o polinômio é 0x1021 e mais nada. Você continua com quatro candidatos, e escolher errado devolve um valor tão plausível quanto o certo.
O hábito que vale cultivar: cite parâmetros, não nomes. Escrever poly=0x1021, init=0xFFFF, refin=false, refout=false, xorout=0x0000 no seu documento de protocolo ocupa uma linha e elimina a ambiguidade de vez. Escrever “CRC-16/CCITT” não.
4. Como identificar qual variante de CRC-16 você tem em mãos
O catálogo resolve isso com uma impressão digital. Toda entrada publica um valor de verificação (check): o CRC dos nove bytes ASCII 123456789. Passe essa string pela implementação que está na sua frente e depois procure o 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 (o CCITT de verdade) | 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 |
Um detalhe afunda um número surpreendente dessas comparações: 123456789 significa os nove bytes 31 32 33 34 35 36 37 38 39, não o número 123456789 e não uma string terminada em nulo. Se a sua linguagem entrega à função uma string larga ou acrescenta um terminador, você está calculando o hash de outra entrada e nenhuma linha vai bater. Cargas úteis não ASCII trazem uma segunda armadilha: o mesmo texto codificado em UTF-8 e em Windows-1252 são duas sequências de bytes diferentes e, portanto, dois CRCs diferentes. A tabela ASCII e conversor mostra o byte de cada caractere, o que leva dez segundos e descarta a dúvida.
Se o valor de verificação não bate com nenhuma linha, descarte primeiro as causas banais antes de suspeitar de um polinômio próprio:
- Ordem dos bytes: você pode estar lendo o resultado invertido do fio.
- Campos cobertos: o remetente inclui ou exclui um byte de endereço, um byte de comprimento ou o próprio campo CRC.
- Um
initdo fabricante que não é nem0x0000nem0xFFFF; isso acontece em protocolos proprietários de medidores e costuma ficar enterrado numa nota de rodapé.
Descobrindo os parâmetros por força bruta
Quando o fabricante não conta e você não consegue ler o firmware dele, peça só uma coisa: um quadro capturado junto com o CRC que o dispositivo produziu para ele. Depois é só enumerar. Dois polinômios, dois valores de init, refin e refout independentes e dois valores de xorout dão 32 combinações, ou seja, nada:
target = 0x4B37 # o valor que a implementação deles retornou
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
Um acerto, e ele é o CRC-16/MODBUS. Alimente o laço com um quadro real em vez de 123456789 e a mesma varredura de 32 vias funciona para qualquer mensagem cujo CRC correto você conheça. Se várias combinações sobreviverem a uma amostra, rode um segundo quadro e cruze os resultados.
5. Modbus RTU na prática
O Modbus RTU usa o CRC-16/MODBUS: polinômio 0x8005, init 0xFFFF, refletido na entrada e na saída, sem XOR final. Para a nossa requisição de seis bytes o CRC é 0xCDC5, e o quadro completo fica assim:
01 03 00 00 00 0A C5 CD
A ordem no fio é o inverso de como você escreve o valor
O valor é 0xCDC5. Os bytes anexados ao quadro são C5 CD. O Modbus especifica o byte baixo do CRC primeiro, o contrário da ordem em que você lê o número hexadecimal, e isso pega as pessoas todas as vezes. Todos os outros campos de vários bytes no mesmo quadro, inclusive aquela contagem de registradores 00 0A, são big-endian. O CRC é a exceção.
Daí sai uma consequência útil. Calcule o CRC-16/MODBUS sobre o quadro completo, com os bytes do CRC incluídos, e o resultado é:
0x0000
Essa é a rotina de validação inteira do receptor. Não é preciso separar os dois bytes finais, trocá-los de ordem e comparar com um valor calculado localmente. Rode o CRC sobre tudo o que chegou e verifique se dá zero. Menos passos significa menos lugares onde inverter os bytes errado.
Por que a documentação do Modbus mostra 0xA001
Abra quase qualquer implementação de Modbus e a constante no código-fonte é 0xA001, não 0x8005. As duas estão certas. 0xA001 é 0x8005 com seus 16 bits invertidos, e pertence à forma refletida do algoritmo, na qual o registrador desloca para a direita em vez de para a esquerda e os bytes de entrada dispensam a inversão individual. As duas implementações produzem saída idêntica; só as tripas mudam. Ver 0xA001 no código é um sinal confiável de que você está diante de um CRC-16 refletido, e não de um polinômio diferente.
Um parêntese para quem estiver lendo uma captura. O Modbus ASCII é um enquadramento à parte, que carrega cada byte como dois caracteres hexadecimais entre um 3A (:) inicial e um 0D 0A final, e usa um LRC em vez de um CRC. Se o seu trace mostra 3A 30 31 30 33 onde você esperava 01 03, você está em modo ASCII e nenhuma variante de CRC vai bater. A tabela ASCII mapeia esses bytes de volta para caracteres.
O mesmo algoritmo em C
O firmware Modbus quase nunca usa o modelo em Python acima. Ele emprega diretamente a forma refletida: deslocar à direita e aplicar XOR com 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;
}
Com o quadro 01 03 00 00 00 0A, esta função devolve 0xCDC5, o mesmo valor que o motor em Python produziu. A string de verificação 123456789 dá 0x4B37. Ambos foram executados no Apple clang 21 e coincidem bit a bit com a tabela da seção 4.
6. refin/refout não é endianness
A confusão sai cara justamente porque um quadro Modbus carrega as duas coisas ao mesmo tempo, e quem mexe na errada troca um sintoma por outro sem chegar mais perto.
Endianness trata de bytes dentro de um valor de vários bytes: se 0xCDC5 é armazenado ou transmitido como CD C5 ou como C5 CD. Nada se move dentro de um byte. Esse é o nível que o guia big-endian versus little-endian cobre por inteiro, e este artigo não vai repetir.
Reflexão fica um nível abaixo, nos bits dentro de um único byte. refin inverte os oito bits de cada byte antes que o registrador o veja: o bit 0 vira o bit 7. A posição do byte na mensagem nunca muda. Endianness não enxerga isso, e a reflexão não enxerga endianness.
Num quadro Modbus os dois acontecem, de forma independente:
- Dentro do algoritmo,
refinerefoutsão true, então os bits são invertidos dentro dos bytes. - No fio, o CRC pronto é anexado com o byte baixo primeiro, o que é uma decisão de ordem de bytes tomada pelo protocolo.
Desligar a reflexão para “consertar” um problema de ordem de bytes produz uma variante completamente diferente, e trocar os bytes finais do quadro para compensar uma reflexão errada produz um valor errado de um jeito novo. Diagnostique um de cada vez: acerte primeiro o valor de verificação só com o algoritmo, depois cuide do layout do quadro.
7. CRC-32 ou CRC-16: qual você deve usar?
O CRC-32 tem o mesmo problema do CRC-16, só que de forma menos visível, porque uma variante domina tão completamente que a maioria dos desenvolvedores nunca fica sabendo que existem outras.
| Variante | check | poly | refin/refout |
|---|---|---|---|
| CRC-32/ISO-HDLC | 0xCBF43926 | 0x04C11DB7 | true |
| CRC-32/BZIP2 | 0xFC891918 | 0x04C11DB7 | false |
| CRC-32C (Castagnoli) | 0xE3069283 | 0x1EDC6F41 | true |
O ISO-HDLC é o que zlib.crc32 calcula, e ele está em toda parte no lado web da pilha. O zip guarda um por entrada no diretório central, e todo chunk de PNG termina com um cobrindo o tipo e os dados do chunk. A sequência de verificação de quadro da Ethernet usa o mesmo conjunto de parâmetros, no fim de cada quadro que o seu servidor envia. O BZIP2 é o mesmo polinômio com as reflexões desligadas, o que produz um valor que não tem nada em comum com o do ISO-HDLC.
O CRC-16 também aparece na mesma vizinhança: o Redis Cluster roteia uma chave para um dos seus 16.384 slots com CRC16(key) mod 16384, e é por isso que chaves com a mesma hash tag caem no mesmo nó.
Por que o CRC-32C ganhou na loteria do hardware
O CRC-32C usa um polinômio diferente, com detecção de erros melhor nos blocos curtos que os protocolos de armazenamento e de rede realmente enviam. Isso lhe rendeu o iSCSI, os checksums de metadados do ext4, o SCTP e o Btrfs. Depois a Intel o colocou no silício: a instrução crc32 do SSE4.2 calcula CRC-32C diretamente, o que o transforma numa verificação de integridade praticamente gratuita no x86 moderno. Se você está escolhendo um CRC para código novo hoje e não tem restrição de compatibilidade, é este que deve pegar.
Nenhum deles é um hash para verificar um download. Quando você quer um checksum para confirmar que um arquivo chegou inteiro, o gerador de hash MD5 é a ferramenta mais familiar, e MD5 versus SHA-256 explica em qual digest confiar para cada coisa. A divisão é essa: um CRC responde “isto foi corrompido?”, um hash criptográfico responde “isto é exatamente o conteúdo que eu espero?”.
8. Um CRC não impede adulteração
Aqui estão duas mensagens JSON com sentidos opostos e o mesmo 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 essas mesmas duas:
A -> a83b90f5e3f21dd32039e0e693a8b15864eda83e258c115deeee50a60c09ae23
B -> f9319ee0507d719c7260535dff33df721ddd9c0e1857690f3d8e02f92601b742
A colisão em si não é a parte interessante; interessante é como ela foi obtida. Ninguém quebrou nada por força bruta aqui: os bytes finais saíram de uma conta. O CRC é uma função linear, e anexar quatro bytes a uma mensagem mapeia na saída do CRC-32 como uma bijeção, de modo que, para qualquer mensagem e qualquer valor alvo, existe exatamente um sufixo de quatro bytes que leva até lá. Achá-lo é aritmética.
A demonstração, anexando os quatro bytes resolvidos 46 C9 6E 0B a hello world:
CRC-32(b'hello world' + 46 C9 6E 0B) = 0xDEADBEEF target 0xDEADBEEF MATCH
Ou seja, um atacante capaz de modificar o seu payload também consegue ajustar o seu CRC, em tempo constante, sem precisar procurar nada. Prefixar a mensagem com um segredo não salva: para uma edição que preserve o comprimento, a correção aplicada ao CRC depende apenas dos bits que mudaram, então dá para calculá-la sem jamais conhecer o segredo. Esse é o formato do ataque que quebrou o WEP.
O CRC defende contra ruído de transmissão, que é aleatório e não quer nada. Contra um adversário, que escolhe onde bater, ele não defende. Quando o requisito é autenticidade e não integridade, você precisa de uma construção com chave: o gerador de HMAC produz uma sobre qualquer payload e chave, e por que a verificação de assinatura de webhook falha percorre as partes que derrubam as pessoas na hora de ligar isso a um endpoint de verdade.
9. Perguntas frequentes
CRC-16/CCITT é a mesma coisa que CRC-16/CCITT-FALSE?
Não, CRC-16/CCITT-FALSE e CRC-16/CCITT são variantes diferentes. O CCITT-FALSE é o CRC-16/IBM-3740: init 0xFFFF, sem reflexão, valor de verificação 0x29B1. A variante que se costuma querer dizer com um CCITT sem sobrenome é o CRC-16/KERMIT: init 0x0000, refletido, check 0x2189. Os dois compartilham o polinômio 0x1021 e não concordam em mais nada.
Por que uma calculadora online discorda do meu dispositivo?
Parâmetros diferentes, quase sempre. A ferramenta assume uma variante por padrão e o dispositivo implementa outra. Passe os bytes ASCII 123456789 pelos dois, compare os dois valores de verificação com a tabela da seção 4 e o parâmetro divergente costuma se entregar em menos de um minuto.
Por que o Modbus coloca o byte baixo do CRC primeiro?
Porque a especificação manda, e esse é o único campo do quadro que se comporta assim. Endereços e contagens de registradores são big-endian; o CRC final não é. Calcule o CRC-16/MODBUS sobre o quadro inteiro, incluindo esses dois bytes, e verifique se dá 0x0000, em vez de reordená-los por conta própria.
O que exatamente refin e refout invertem?
Bits dentro de um byte, nunca bytes dentro de uma mensagem. refin inverte os oito bits de cada byte de entrada antes do processamento; refout inverte o registrador final antes do último XOR. Os dois são independentes de endianness, que governa como um valor de vários bytes é disposto no fio.
Devo usar CRC-16 ou CRC-32?
O CRC-16 deixa passar uma corrupção aleatória mais ou menos uma vez a cada 65.536; o CRC-32, uma vez a cada 4,3 bilhões. Para quadros seriais curtos, de algumas dezenas de bytes, o CRC-16 dá conta e normalmente já é exigido pelo protocolo de qualquer forma. Para arquivos, quadros de rede e qualquer coisa acima de alguns kilobytes, use CRC-32.
Dá para usar um CRC como assinatura de API?
Não, um CRC não pode servir como assinatura de API. O CRC é linear, então quem alterar o payload consegue recalcular um valor que bate, e anexar quatro bytes escolhidos alcança qualquer alvo de CRC-32 que você indicar. Assinaturas precisam de chave secreta e de uma construção não linear. Use HMAC com SHA-256.
Dá para recuperar os dados originais a partir de um valor de CRC?
Não, os dados originais não podem ser recuperados a partir de um valor CRC. Um CRC-16 comprime qualquer entrada em 16 bits, então infinitas mensagens compartilham cada valor. Mesmo assim, o sentido inverso continua útil para atacantes: dado um valor alvo, você consegue construir uma mensagem que o produz, e é exatamente por isso que um CRC não autentica nada.
O documento do fabricante só diz “CRC-16”. Como fixo os parâmetros?
Peça um quadro capturado mais o CRC que o dispositivo calculou para ele e rode a varredura de 32 combinações da seção 4 contra esse par. Se sobrar mais de um conjunto de parâmetros, repita com um segundo quadro e fique com a interseção. Duas amostras quase sempre resolvem.