用 C 语言在消息的开头使用 CRC 哈希计算 CRC32

Posted

技术标签:

【中文标题】用 C 语言在消息的开头使用 CRC 哈希计算 CRC32【英文标题】:CRC32 calculation with CRC hash at the beginning of the message in C 【发布时间】:2015-10-13 15:01:39 【问题描述】:

我需要计算消息的 CRC 并将其放在此消息的 开头,以便带有“前置”补丁字节的消息的最终 CRC 等于 0。我能够做到在几篇文章的帮助下,这很容易,但不适用于我的具体参数。问题是我必须使用给定的 CRC32 算法来计算内存块的 CRC,但我没有计算这 4 个补丁字节/“CRC 类型”的“反向”算法。给定的 CRC32 算法的参数是:

多项式:0x04C11DB7 字节序:大字节序 初始值:0xFFFFFFFF 反映:假 异或输出: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 个元素,但此表实际上包含 first 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;

这是预期的结果 - 前 2 个 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:Calculating CRC initial value instead of appending the CRC to payload) :

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),但我认为所有这些解释都是必要的。提前感谢您的见解或建议。

【问题讨论】:

您的 CRC 计算完全搞砸了。使用来自消息的位和 CRC 的高位的异或来选择表条目。对于您的按位 CRC 例程,您决定是否与POLY 互斥或与^ *data 无关,因此甚至不需要存在!正确的做法是将数据向上移动到CRC的顶部,然后再决定高位。您没有计算您指定的 CRC,顺便说一下,它是 MPEG2 CRC-32。 嗯,你是对的,那些 CRC 的计算是不正确的,但我无法改变这一点,因为原始的 CRC 计算和表格与算法参数一起提供给我,我只需要找到这 4 个补丁字节。如果官方 CRC-32/MPEG-2 算法在我的问题中通过了该测试流,那么我想我是这里错的那个人,但我认为谁制作了那个算法(给我的那个)没有遵循他应该的规格。但是与 *data 的 XOR 是我的错误,真的...原来是 (... & 1),所以我只是将 1 更改为 0x80000000 认为它会有所帮助:D 您应该发布原始的 CRC 计算和表格,没有任何变化。也许原版没问题。 好吧,表格是一样的,计算也是一样的,它分为3个函数,所以我把它们放在一起..但是正如我写的那样,那个短表应该包含大的每16个元素表(至少这是我读到的),但是这个表包含大表的 16 个元素,我认为这是问题 您不应将校验和称为 CRC,因为您计算的不是 CRC。 【参考方案1】:

我将回答您的 CRC 规范,即CRC-32/MPEG-2。我将不得不忽略您计算该 CRC 的尝试,因为它们不正确。

无论如何,为了回答你的问题,我碰巧写了一个程序来解决这个问题。它被称为spoof.c。它非常快速地计算出消息中要更改的位以获得所需的 CRC。它按 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;

并使用问题中的表格使用 nybble-wise 算法(这是正确的):

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 :)【参考方案2】:

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

编辑:最初的答案来自用户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));

【讨论】:

它是“samgak”。答案已被所有者 samgak 删除。 那是Samgak - 显然他删除了他的答案,因为你的函数“不是CRC32的正确实现”,即使他的解决方案有效(基本上,他同意马克阿德勒的观点)。因此,您可能需要再看一遍,以确认一下。 谢谢你的名字,我希望他能再发一次。我也知道这不是正确/通常的实现,这就是为什么我在尝试以通常的方式进行一周后在这里写信的原因。有人实现了这种CRC32,我对此无能为力,给我的一切都在我问题的第一段中给了你。再次感谢您 你已经说过这实际上不是你得到的。您将获得的内容修改为“将所有内容放在一起”。 您没有冒犯,但如果您不显示最初提供的内容,我们将无法帮助您。你假设你在“缩短代码”时没有搞砸什么,这样它“可读性更好”。但是,我们已经有一个示例,其中“与 *data 的 XOR 是我的错误,是的”,“认为它会有所帮助”。【参考方案3】:

替代方法。假设xorout = 0,如果不是,则计算出正常的crc后,再用crc ^= xorout去掉它。这里的方法将正常的 crc 乘以 (1/2)%(crc polynomial) 提高到(消息大小以位为单位)幂 %(crc 多项式),相当于向后循环。如果消息大小是固定的,则映射是固定的,时间复杂度为 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;

【讨论】:

以上是关于用 C 语言在消息的开头使用 CRC 哈希计算 CRC32的主要内容,如果未能解决你的问题,请参考以下文章

CRC的C语言的程序

crc16校验的c语言程序

一个简短的CRC32计算程序 C语言

C语言零基础,怎么用C语言实现CRC16检验码

CRC校验的实现(C语言,CRC16)

CRC校验的实现(C语言,CRC16)