crc16 求未知多项式 ploy 和crc初始值 init
背景:
破解老旧电气设备通讯,使用串口助手截获数据(Hex)并分析,假定各字节作用——校验码两字节16位,尝试常见 crc16 计算校验码均失败。
目标:
根据已有数据,通过编程计算出未知多项式 ploy 和crc初始值 init 。
编程思路:
crc16 的 ploy 和 init 均为16位(0~0xFFFF),两层for循环尝试所有 poly 和 init 的组合可能(65536*65536),使poly和init能代入已有数据。
编程环境:
计算机:联想拯救者Y7000 2019 PG0 (CPU:英特尔 Core i5-9300H @ 2.40GHz 四核,内存:8 GB ,记忆科技 DDR4 2666MHz )
IDE:Visual Studio 2019
编程语言:C#
其它:.Net 5.0 + Windows 应用程序
代码效果(尝试所有 poly 和 init 的组合可能所花费的时间):
1.采用 crc 直接计算法,60+分钟。
2.采用 crc 全字节查表法,< 4分钟。
代码( crc 直接计算法):
#region crc16求未知多项式ploy和crc初始值init
/// <summary>
/// crc16求未知多项式ploy和crc初始值init
/// </summary>
public class CRC16_Get_Polynomial_InitialValue
{
static CRC16_Get_Polynomial_InitialValue()
{
}
/// <summary>
/// crc16求未知多项式ploy和crc初始值init
/// </summary>
/// <returns></returns>
public ushort CRC16_get_Polynomial_InitialValue()
{
//已知参与计算校验码的数据(除帧头、固定字段、校验位、帧尾以外的数据)
byte[] date1 = { 0x01, 0x04, 0x0C, 0x01, 0x00, 0x32, 0x00 };
byte[] date2 = { 0x01, 0x04, 0x0C, 0x01, 0x00, 0x31, 0x00 };
byte[] date3 = { 0x01, 0x04, 0x0A, 0x01, 0x00, 0x0C, 0x00 };
//已知数据的校验码
ushort crc1 = 0x59D8;
ushort crc2 = 0x31F2;
ushort crc3 = 0x73CF;
//假设计算得到校验码后交换高低字节数据,还原校验码未交换高低字节前的数据
ushort crc11 = (ushort)(crc1 << 8 | crc1 >> 8);
ushort crc22 = (ushort)(crc2 << 8 | crc2 >> 8);
ushort crc33 = (ushort)(crc3 << 8 | crc3 >> 8);
bool get = false;//用于得到poly和init后终止for循环
//poly为多项式, 使用for循环来尝试不同的poly值
for (int poly = 0; poly <= 0xFFFF; poly++)
{
//校验得出的poly是否唯一
//if (poly == 0x8408)
//{
// continue;
//}
//init为crc初始值,使用for循环来尝试不同的init值
for (int init = 0; init <= 0xFFFF; init++)
{
ushort cRc_16 = Get_cRc_16(date1, init, poly);
if (cRc_16 == crc1)
{
cRc_16 = Get_cRc_16(date2, init, poly);
if (cRc_16 == crc2)
{
cRc_16 = Get_cRc_16(date3, init, poly);
if (cRc_16 == crc3)
{
MessageBox.Show("poly为" + poly.ToString("X2") + ",init为" + init.ToString("X2") + "。");
get = true;
break;//终止for循环
}
}
}
else if (cRc_16 == crc11)
{
cRc_16 = Get_cRc_16(date2, init, poly);
if (cRc_16 == crc22)
{
cRc_16 = Get_cRc_16(date3, init, poly);
if (cRc_16 == crc33)
{
MessageBox.Show("poly为" + poly.ToString("X2") + ",init为" + init.ToString("X2") + ",得到的校验码需要高低字节互换。");
get = true;
break;
}
}
}
}
if (get)
{
break;
}
}
if (!get)
{
MessageBox.Show("计算失败,请检查程序逻辑和操作的数据");
}
return 0;
}
/// <summary>
/// crc16直接计算法
/// </summary>
/// <param name="date"></param>
/// <param name="init"></param>
/// <param name="poly"></param>
/// <returns></returns>
private static ushort Get_cRc_16(byte[] date, int init, int poly)
{
ushort cRc_16 = (ushort)init;
for (int i = 0; i < date.Length; i++) // 遍历字节数组date
{
cRc_16 = (ushort)(cRc_16 ^ date[i]); // 对cRc_16进行异或操作
for (int j = 0; j < 8; j++)
{
if ((cRc_16 & 0x0001) != 0) // 如果cRc_16的最低位为1
{
cRc_16 = (ushort)((cRc_16 >> 1) ^ poly); // 右移一位并与poly进行异或操作
}
else // 如果cRc_16的最低位为0
{
cRc_16 = (ushort)(cRc_16 >> 1); // 直接右移一位
}
}
}
return cRc_16;
}
#endregion
代码(crc 全字节查表法):
#region crc16求未知多项式ploy和crc初始值init
/// <summary>
/// crc16求未知多项式ploy和crc初始值init
/// </summary>
public class CRC16_Get_Polynomial_InitialValue
{
static CRC16_Get_Polynomial_InitialValue()
{
}
/// <summary>
/// crc16求未知多项式ploy和crc初始值init
/// </summary>
/// <returns></returns>
public ushort CRC16_get_Polynomial_InitialValue()
{
//已知参与计算校验码的数据(除帧头、固定字段、校验位、帧尾以外的数据)
byte[] date1 = { 0x01, 0x04, 0x0C, 0x01, 0x00, 0x32, 0x00 };
byte[] date2 = { 0x01, 0x04, 0x0C, 0x01, 0x00, 0x31, 0x00 };
byte[] date3 = { 0x01, 0x04, 0x0A, 0x01, 0x00, 0x0C, 0x00 };
//已知数据的校验码
ushort crc1 = 0x59D8;
ushort crc2 = 0x31F2;
ushort crc3 = 0x73CF;
//假设计算得到校验码后交换高低字节数据,还原校验码未交换高低字节前的数据
ushort crc11 = (ushort)(crc1 << 8 | crc1 >> 8);
ushort crc22 = (ushort)(crc2 << 8 | crc2 >> 8);
ushort crc33 = (ushort)(crc3 << 8 | crc3 >> 8);
bool get = false;//用于得到poly和init后终止for循环
//poly为多项式, 使用for循环来尝试不同的poly值
for (int poly = 0; poly <= 0xFFFF; poly++)
{
//校验得出的poly是否唯一
//if (poly == 0x8408)
//{
// continue;
//}
GenerateCRCTable(poly);
//init为crc初始值,使用for循环来尝试不同的init值
for (int init = 0; init <= 0xFFFF; init++)
{
ushort cRc_16 = GetCRC16(date1,init);
if (cRc_16 == crc1)
{
cRc_16 = GetCRC16(date2, init);
if (cRc_16 == crc2)
{
cRc_16 = GetCRC16(date3, init);
if (cRc_16 == crc3)
{
MessageBox.Show("poly为" + poly.ToString("X2") + ",init为" + init.ToString("X2") + "。");
get = true;
break;//终止for循环
}
}
}
else if (cRc_16 == crc11)
{
cRc_16 = GetCRC16(date2, init);
if (cRc_16 == crc22)
{
cRc_16 = GetCRC16(date3, init);
if (cRc_16 == crc33)
{
MessageBox.Show("poly为" + poly.ToString("X2") + ",init为" + init.ToString("X2") + ",得到的校验码需要高低字节互换。");
get = true;
break;
}
}
}
}
if (get)
{
break;
}
}
if (!get)
{
MessageBox.Show("计算失败,请检查程序逻辑和操作的数据");
}
return 0;
}
private ushort[] _crcTable = new ushort[256];
/// <summary>
/// 得到校验码表
/// </summary>
/// <param name="poly"></param>
private void GenerateCRCTable(int poly)
{
// 使用两个嵌套的循环来计算每个可能的字节值的CRC-16校验码
for (int i = 0; i < 256; i++)
{
//crc赋值从0到256(00到FF)
ushort crc = (ushort)i;
//MessageBox.Show(string.Join("", crc));
for (int j = 0; j < 8; j++)
{
// 如果当前字节的最高位为1,则将CRC右移一位并与多项式0xA001进行异或操作
if ((crc & 1) != 0)
{
crc = (ushort)((crc >> 1) ^ poly);
}
// 否则,将CRC右移一位
else
{
crc >>= 1;
}
}
// 将计算出的CRC-16校验码存储在_crcTable数组中
_crcTable[i] = crc;
}
}
/// <summary>
/// 全字节查表法
/// </summary>
/// <param name="data"></param>
/// <param name="init"></param>
/// <returns></returns>
public ushort GetCRC16(byte[] data, int init)
{
ushort cRc_16 = (ushort)init;
// 遍历输入数据的每一个字节
foreach (byte b in data)
{
// 使用预先生成的_cRc_16Table数组来计算校验码,并将结果与cRc_16右移8位后的结果进行异或操作
cRc_16 = (ushort)(_crcTable[(cRc_16 ^ b) & 0xFF] ^ (cRc_16 >> 8));
}
//cRc_16高低字节互换
//cRc_16 = (ushort)((cRc_16 << 8) | cRc_16 >> 8);
// 返回计算出的cRc_16-16校验码
return cRc_16;
}
#endregion