设备协议里常见“校验和不对”“CRC 失败”,但 CRC 不是某个唯一算法,校验通过也不等于消息可信。真正落地时,必须同时确定算法参数、覆盖范围、字节顺序和错误后的恢复行为。
1. 校验到底解决什么
校验字段用于发现传输、存储或组帧过程中的偶发错误。常见方法包括:
| 方法 | 特点 | 适用边界 |
|---|---|---|
| XOR | 极易实现,检测能力有限 | 简单遗留协议 |
| 加法和 | 可检测部分错误,容易碰撞 | 资源受限或既有协议 |
| Fletcher / Adler | 计算便宜,能力取决于参数与报文长度 | 软件数据完整性场景 |
| CRC | 对特定错误模式有可分析的检测能力 | 串口、总线、文件和网络帧 |
| 密码学 MAC | 同时验证完整性与共享密钥持有者 | 需要抵抗主动攻击的场景 |
CRC 的检测能力取决于位宽、多项式和受保护消息的最大长度。设计良好的 n 位 CRC 可保证检出长度不超过 n bit 的突发错误;能否保证检出全部单比特、双比特或奇数个 bit 错误,还要检查具体多项式及码字长度。2⁻ⁿ 只能近似描述特定随机错误模型下的残余概率,不能当作所有链路和错误模式的固定漏检率。测试时应先声明目标消息长度和预期检测的错误类别,再选择多项式并注入验证。
CRC 不含秘密。攻击者修改数据后可以重新计算 CRC,因此它不是认证机制。
2. “CRC-16”信息不够
一个 CRC 实例至少由以下参数确定:
- width(位宽,多项式隐含但单独列出更清晰);
- 多项式(poly);
- 初始值(init);
- 输入、输出是否反射(refin/refout);
- 输出异或值(xorout);
- 校验值在线路上的字节顺序;
- CRC 覆盖哪些字段。
两个协议都写“CRC-16”,结果仍可能完全不同。实现前应从设备协议或标准获得完整参数,并用规范测试向量核对,而不是挑一个搜索结果相似的函数。
3. Modbus RTU 的 CRC 示例
Modbus 串行线路规范定义 RTU 帧使用 CRC-16;CRC 字段在消息中先发送低字节,再发送高字节。下面函数返回数值形式的 CRC,序列化时再明确字节顺序:
| |
可用标准检查字符串验证核心实现:ASCII 123456789 的该 CRC 结果应为 0x4B37。这只证明参数化核心吻合;仍要用真实协议帧验证覆盖范围和线路字节序。
4. 覆盖范围要写成可执行规则
“对整帧做 CRC”仍然含糊。应明确类似规则:
| |
若协议含转义,还要说明 CRC 计算的是转义前原始字节还是线路上的转义后字节。若含长度字段,要说明长度是否参与计算。编码器和解码器应共享同一条规则,避免各自拼切片。
5. 校验失败后怎么办
校验失败意味着当前候选帧不可交付,但不一定说明端口已断开。恢复策略取决于定界方式:
- 固定长度帧:丢弃当前候选帧,再寻找下一同步点;
- Magic + 长度:从后续可能的 Magic 重新扫描,同时限制扫描量;
- 静默间隔:丢弃该时间窗口内的消息;
- TCP 长度帧:若头部已失去可信度,通常应关闭会话,避免无限错位解析。
不要把坏帧原样交给业务层“看看能不能用”,也不要在每个 CRC 错误后立刻重连。应记录计数、端口、方向、帧长度和有限的十六进制摘要,避免把整段敏感数据写入日志。
6. 表驱动与硬件实现
查表法能减少逐 bit 计算,硬件 CRC 外设则可进一步降低 CPU 占用。但它们必须与协议参数完全一致。不同 CPU 指令或硬件外设支持的多项式、反射和初值集合并不相同。
先以清晰的参考实现和测试向量建立正确性,再替换为表驱动或硬件加速版本;优化后对同一语料做逐帧差分测试。
7. 测试清单
- 空消息、单字节、全零、全
0xFF; - 标准检查字符串和规范示例帧;
- 分别翻转每一个 bit,确认检测结果符合预期;
- CRC 字节交换、覆盖范围少一个字节等常见错误;
- 编码后再解析的往返测试;
- 与设备抓包、厂商工具或另一独立实现交叉验证。
8. 小结
校验算法的名字不是完整契约。只有参数、覆盖范围和字节序全部一致,双方才会得到相同结果;CRC 的职责是检测偶发错误,而不是认证或加密。把参考向量固化为自动化测试,是避免设备现场反复猜算法的最低成本手段。