在C语言中计算带有CRC哈希值的消息的CRC32校验码

5
我需要计算消息的CRC并将其放置在此消息开头,以便带有“前置”修补字节的消息的最终CRC等于0。借助几篇文章的帮助,我很容易做到了这一点,但对于我的特定参数却不是那么简单。问题在于我必须使用给定的CRC32算法来计算内存块的CRC,但我没有计算那4个修补字节/“某种CRC”的“反向”算法。给定CRC32算法的参数为:
  • 多项式:0x04C11DB7
  • 字节序:大端
  • 初始值:0xFFFFFFFF
  • 反射:false
  • XOR输出为:0L
  • 测试流:0x0123、0x4567、0x89AB、0xCDEF 的结果为 CRC = 0x612793C3

计算CRC的代码(半字节、表驱动,我希望数据类型定义是自说明的):

uint32 crc32tab(uint16* data, uint32 len, uint32 crc)
{
    uint8 nibble;
    int i;
    while(len--)
    {
        for(i = 3; i >= 0; i--)
        {
            nibble = (*data >> i*4) & 0x0F;
            crc = ((crc << 4) | nibble) ^ tab[crc >> 28];
        }
        data++;
    }

    return crc;
}

需要的表格是(我认为短[16]表应该包含大[256]表中每隔16个元素的一个,但实际上这个表格包含 16个元素,但这就是提供给我的方式):
static const uint32 tab[16]=
{
    0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9,
    0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005,
    0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61,
    0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD
};  

我修改了代码,使其不那么冗长,但功能保持不变。问题在于这个正向CRC计算看起来更像是反向的CRC计算。
我花了差不多一周的时间试图找出正确的多项式/算法/表格组合,但没有成功。如果有帮助的话,我想出了一个与上面的表驱动代码相对应的位算法,尽管这并不难。
uint32 crc32(uint16* data, uint32 len, uint32 crc)
{
    uint32 i;
    while(len--)
    {
        for(i = 0; i < 16; i++)
        {
            // #define POLY 0x04C11DB7
            crc = (crc << 1) ^ (((crc ^ *data) & 0x80000000) ? POLY : 0);
        }
        crc ^= *data++;
    }

    return crc;
}

以下是预期结果 - 前两个16位字组成所需的未知CRC校验码,其余部分则是已知的数据本身(将这些示例提供给提供的算法进行计算,结果为0)。
{0x3288, 0xD244, 0xCDEF, 0x89AB, 0x4567, 0x0123}
{0xC704, 0xDD7B, 0x0000} - append as many zeros as you like, the result is the same
{0xCEBD, 0x1ADD, 0xFFFF}
{0x81AB, 0xB932, 0xFFFF, 0xFFFF}
{0x0857, 0x0465, 0x0000, 0x0123}
{0x1583, 0xD959, 0x0123}
   ^        ^
   |        |
   unknown bytes that I need to calculate

我认为在0xFFFF或0x0000单词上进行测试很方便,因为计算方向和字节序不重要(希望如此:D)。因此,小心使用其他测试字节,因为计算方向相当狡猾:D。此外,您可以看到通过仅向算法提供零(向前和向后),结果是所谓的残留物(0xC704DD7B),这可能有所帮助。

所以...我写了至少10个不同的函数(按位、表格、多项式组合等)尝试解决这个问题,但没有成功。我在这里给出我寄予厚望的函数。它是上面那个基于表格的算法的“反向”算法,当然使用不同的表格。问题在于,我从中得到的唯一正确的CRC是所有0的消息,这并不意外。我还编写了按位算法的反向实现(反向移位等),但该算法仅正确返回第一个字节。
这是基于表格的算法,指针data应该指向消息的最后一个元素,输入crc应该是请求的crc(整个消息的0或者您可以采取另一种方法——消息的最后4个字节是您正在寻找的CRC:计算CRC初始值而不是将CRC附加到有效负载):

uint32 crc32tabrev(uint16* data, uint32 len, uint32 crc)
{
    uint8 nibble;
    int i;
    while(len--)
    {
        for(i = 0; i < 4; i++)
        {
            nibble = (*data >> i*4) & 0x0F;
            crc = (crc >> 4) ^ revtab[((crc ^ nibble) & 0x0F)];
        }
        data--;
     }

     return reverse(crc); //reverse() flips all bits around center (MSB <-> LSB ...) 
}

这个表格,我希望它是“被选中的那个”。
static const uint32 revtab[16]=
{
    0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC,
    0x76DC4190, 0x6B6B51F4, 0x4DB26158, 0x5005713C,
    0xEDB88320, 0xF00F9344, 0xD6D6A3E8, 0xCB61B38C,
    0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C
};

正文翻译:如你所见,这个算法有一些优点,使我陷入了困境。我觉得我可能正在正确的轨道上,但是我缺少某些东西。我希望多一个人的眼睛能看到我看不到的。对于长篇幅的帖子(没有土豆:D),我很抱歉,但我认为所有的解释都是必要的。提前感谢您的洞见或建议。
注:原文中出现了 "no potato",这里翻译成了“没有土豆”,但它并不影响原文的意思,仅仅是一个玩笑话。

2
你的CRC计算完全混乱了。表项是使用消息和CRC的高位的异或来选择的。对于您的逐位CRC例程,您是否与“POLY”进行异或的决定独立于“^ *data”,因此甚至不需要出现在那里!正确的方法是将数据向上移动到CRC的顶部,然后决定高位。您没有计算指定的CRC,顺便说一下,这是MPEG2 CRC-32。 - Mark Adler
你说得对,那些CRC计算是错误的,但我不能更改,因为那个原始的CRC计算和表是与算法参数一起给我的,而我只需要找到那四个补丁字节。如果官方的CRC-32/MPEG-2算法能够通过我提出的测试流,那么我想我就错了,但我认为制作那个算法的人(给我的那个)没有按照应该的规范来做。但是那个*XOR with data是我的失误,正确的是(...&1),所以我只是将1改成了0x80000000,以为这样会有帮助 :D - LStarling
好的,表格和计算都是一样的,它被分成了3个函数,所以我把它们都放在一起了。但正如我所写的那样,这个短表应该包含大表的每16个元素(至少这是我读到的),但是这个表只包含大表的前16个元素,我认为这就是问题所在。 - LStarling
所以你改了它。你应该发布原始版本_没有任何更改_. - Mark Adler
1
你不应该将你的校验和称作 CRC,因为你计算的不是 CRC。 - Kuba hasn't forgotten Monica
显示剩余2条评论
3个回答

7
我将回答关于CRC规范的问题,即CRC-32/MPEG-2。由于您的计算不正确,我将忽略您对该CRC的计算尝试。
无论如何,为了回答您的问题,我恰好编写了一个解决此问题的程序。它被称为spoof.c。它非常快速地计算出在消息中更改哪些位以获得所需的CRC。 它在O(log(n))时间内完成,其中n是消息的长度。这里有一个例子:
让我们采取九字节消息123456789(这些数字用ASCII表示)。 我们将在前面添加四个零字节,我们将更改它们以获得所需的CRC。 消息的十六进制为:00 00 00 00 31 32 33 34 35 36 37 38 39。 现在我们计算该消息的CRC-32/MPEG-2。 我们得到373c5870
现在我们使用此输入运行spoof,其中包括CRC长度(以位为单位),它不是反射的事实,多项式,我们刚刚计算的CRC,消息长度(以字节为单位)以及第一四个字节中的所有32位位置(这是我们允许spoof更改的内容)。
32 0 04C11DB7
373c5870 13
0 0 1 2 3 4 5 6 7 
1 0 1 2 3 4 5 6 7
2 0 1 2 3 4 5 6 7
3 0 1 2 3 4 5 6 7

它会根据前四个字节中设置的位,输出以下内容:
invert these bits in the sequence:
offset bit
     0 1
     0 2
     0 4
     0 5
     0 6
     1 0
     1 2
     1 5
     1 7
     2 0
     2 2
     2 5
     2 6
     2 7
     3 0
     3 1
     3 2
     3 4
     3 5
     3 7

我们将前四个字节设置为:76 a5 e5 b7。然后,我们通过计算消息76 a5 e5 b7 31 32 33 34 35 36 37 38 39的CRC-32/MPEG-2来进行测试,得到00000000,这是期望的结果。
您可以根据需要调整spoof.c
以下是一个示例,正确使用位运算算法计算一系列字节的CRC-32/MPEG-2:
uint32_t crc32m(uint32_t crc, const unsigned char *buf, size_t len)
{
    int k;

    while (len--) {
        crc ^= (uint32_t)(*buf++) << 24;
        for (k = 0; k < 8; k++)
            crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1;
    }
    return crc;
}

使用问题中正确的表格,并采用逐四位字节的算法:
uint32_t crc_table[] = {
    0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9,
    0x130476DC, 0x17C56B6B, 0x1A864DB2, 0x1E475005,
    0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61,
    0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD
};

uint32_t crc32m_nyb(uint32_t crc, const unsigned char *buf, size_t len)
{
    while (len--) {
        crc ^= (uint32_t)(*buf++) << 24;
        crc = (crc << 4) ^ crc_table[crc >> 28];
        crc = (crc << 4) ^ crc_table[crc >> 28];
    }
    return crc;
}

在这两种情况下,初始CRC必须为0xffffffff

谢谢你的回答,我相信有人会好好利用它,但在我的情况下,计算CRC的CRC算法有点特殊,并不遵循规范(CRC-32/MPEG-2),尽管它声称是这样的(至少我认为是这样)。上面的回答通过蛮力解决了这个问题,但那个回答已经被删除了,我不知道为什么,也不知道最初是谁发布的。但再次感谢你的回答,它肯定会帮助到有类似问题和遵循规范的CRC生成器的人们。 :D :) - LStarling

1

备选方法。假定xorout = 0,如果不是,则在计算正常crc之后,然后crc ^= xorout以去除它。这里的方法将正常的crc乘以(1/2)%(crc polynomial)的(message size in bits)次方%crc polynomial,相当于向后循环。如果消息大小固定,则映射固定且时间复杂度为O(1)。否则,它是O(log(n))。

此示例代码使用Visual Studio和无进位乘法的内置函数(PCLMULQDQ),它使用XMM(128位)寄存器。Visual Studio使用__m128i类型表示整数XMM值。

#include <stdio.h>
#include <stdlib.h>
#include <intrin.h>

typedef unsigned char       uint8_t;
typedef unsigned int       uint32_t;
typedef unsigned long long uint64_t;

#define POLY  (0x104c11db7ull)
#define POLYM ( 0x04c11db7u)

static uint32_t crctbl[256];

static __m128i poly;                    /* poly */
static __m128i invpoly;                 /* 2^64 / POLY */

void GenMPoly(void)                     /* generate __m128i poly info */
{
uint64_t N = 0x100000000ull;
uint64_t Q = 0;
    for(size_t i = 0; i < 33; i++){
        Q <<= 1;
        if(N&0x100000000ull){
            Q |= 1;
            N ^= POLY;
        }
        N <<= 1;
    }
    poly.m128i_u64[0] = POLY;
    invpoly.m128i_u64[0] = Q;
}

void GenTbl(void)                       /* generate crc table */
{
uint32_t crc;
uint32_t c;
uint32_t i;
    for(c = 0; c < 0x100; c++){
        crc = c<<24;
        for(i = 0; i < 8; i++)
            /* assumes twos complement */
            crc = (crc<<1)^((0-(crc>>31))&POLYM);
        crctbl[c] = crc;
    }
}

uint32_t GenCrc(uint8_t * bfr, size_t size) /* generate crc */
{
uint32_t crc = 0xffffffffu;
    while(size--)
        crc = (crc<<8)^crctbl[(crc>>24)^*bfr++];
    return(crc);
}

/* carryless multiply modulo poly */
uint32_t MpyModPoly(uint32_t a, uint32_t b) /* (a*b)%poly */
{
__m128i ma, mb, mp, mt;
    ma.m128i_u64[0] = a;
    mb.m128i_u64[0] = b;
    mp = _mm_clmulepi64_si128(ma, mb, 0x00);      /* p[0] = a*b */
    mt = _mm_clmulepi64_si128(mp, invpoly, 0x00); /* t[1] = (p[0]*((2^64)/POLY))>>64 */
    mt = _mm_clmulepi64_si128(mt, poly, 0x01);    /* t[0] = t[1]*POLY */
    return mp.m128i_u32[0] ^ mt.m128i_u32[0];     /* ret =  p[0] ^ t[0] */
}

/* exponentiate by repeated squaring modulo poly */
uint32_t PowModPoly(uint32_t a, uint32_t b)     /* pow(a,b)%poly */
{
uint32_t prd = 0x1u;                    /* current product */
uint32_t sqr = a;                       /* current square */
    while(b){
        if(b&1)
            prd = MpyModPoly(prd, sqr);
        sqr = MpyModPoly(sqr, sqr);
        b >>= 1;
    }
    return prd;
}

int main()
{
uint32_t inv;                               /* 1/2 % poly, constant */
uint32_t fix;                               /* fix value, constant if msg size fixed */
uint32_t crc;                               /* crc at end of msg */
uint32_t pre;                               /* prefix for msg */
uint8_t msg[13] = {0x00,0x00,0x00,0x00,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39};

    GenMPoly();                             /* generate __m128i polys */
    GenTbl();                               /* generate crc table */
    inv = PowModPoly(2, 0xfffffffeu);       /* inv = 2^(2^32-2) % Poly = 1/2 % poly */
    fix = PowModPoly(inv, 8*sizeof(msg));   /* fix value */
    crc = GenCrc(msg, sizeof(msg));         /* calculate normal crc */
    pre = MpyModPoly(fix, crc);             /* convert to prefix */
    printf("crc = %08x pre = %08x ", crc, pre);
    msg[0] = (uint8_t)(pre>>24);            /* store prefix in msg */
    msg[1] = (uint8_t)(pre>>16);
    msg[2] = (uint8_t)(pre>> 8);
    msg[3] = (uint8_t)(pre>> 0);
    crc = GenCrc(msg, sizeof(msg));         /* check result */
    if(crc == 0)
        printf("passed\n");
    else
        printf("failed\n");
    return 0;
}

0

在我提问几个小时后,我不记得名字的某个人回答了我的问题,结果证明是正确的。但不知何故,这个答案被彻底删除了,我不知道为什么或是谁做的,但我想感谢这个人,如果你看到了,请再次发表你的答案,我会删除这个。但对于其他用户,这是他的答案,对我有用,再次感谢神秘的人(不幸的是,我不能完全复制他的笔记和建议,只有代码本身):

编辑:原始答案来自用户 samgak,所以在他发布答案之前,这个留着。

反向 CRC 算法:

uint32 revcrc32(uint16* data, uint32 len, uint32 crc)
{
     uint32 i;
     data += len - 1;

     while(len--)
     {
         crc ^= *data--;
         for(i = 0; i < 16; i++)
         {
             uint32 crc1 = ((crc ^ POLY) >> 1) | 0x80000000;
             uint32 crc2 = crc >> 1;
             if(((crc1 << 1) ^ (((crc1 ^ *data) & 0x80000000) ? POLY : 0)) == crc)
                 crc = crc1;
             else if(((crc2 << 1) ^ (((crc2 ^ *data) & 0x80000000) ? POLY : 0)) == crc)
                 crc = crc2;
         }
     }
     return crc;
}

查找修补字节:

#define CRC_OF_ZERO 0xb7647d
void bruteforcecrc32(uint32 targetcrc)
{
    // compute prefixes:
    uint16 j;
    for(j = 0; j <= 0xffff; j++)
    {
        uint32 crc = revcrc32(&j, 1, targetcrc);
        if((crc >> 16) == (CRC_OF_ZERO >> 16))
        {
           printf("prefixes: %04lX %04lX\n", (crc ^ CRC_OF_ZERO) & 0xffff, (uint32)j);
           return;
        }
    }
}

使用方法:

uint16 test[] = {0x0123, 0x4567, 0x89AB, 0xCDEF};  // prefix should be 0x0CD8236A

bruteforcecrc32(revcrc32(test, 4, 0L));

1
那是Samgak - 显然他删除了他的答案,因为你的函数“不是正确的CRC32实现”,尽管他的解决方案有效(基本上,他同意Mark Adler的观点)。所以你可能想再看一下它,以确保它正确。 - Jongware
1
感谢提供名称,希望他能再次发布。我知道这不是正确/通常的实现方式,这就是为什么在尝试了一个星期后我才写到这里。有人实现了这种CRC32,我无法做太多事情,我的问题第一段中给出了所有给我的东西,现在也给了你。再次感谢。 - LStarling
也许吧,但结果是一样的,我基本上是将 i++ 改成了 i = i + 1,但我不想争论,我只是说这些修改是为了缩短代码,对于那些知道 crc 工作原理的人来说,它更易读...如果我以某种方式冒犯了你或社区,我很抱歉。 - LStarling
1
你没有冒犯,但是如果你不展示原始提供的内容,我们无法为你提供帮助。你假设在“缩短代码”以使其更易读时没有弄错任何东西。然而,我们已经有一个例子,“那个XOR与*数据是我的错误,真的”,“认为它会有所帮助”。 - Mark Adler
你又说对了,但我假设我没有搞砸什么,只是因为返回的crc仍然相同,这可能不是很聪明的做法,但我们正在谈论crc,每个小错误都会导致不良结果,但我相信你也有合理的回应,所以我们不应该继续这个离题的评论... ...再次感谢你的时间,因为我在这个单一的线程中学到的比其他任何问题都要多,因为它解决了我的确切问题。 - LStarling
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接