¿Cómo se calcula una suma de comprobación CRC32?

102

Tal vez simplemente no lo esté viendo, pero CRC32 parece innecesariamente complicado o no está suficientemente explicado en cualquier lugar que pueda encontrar en la web.

Entiendo que es el resto de una división aritmética no basada en acarreo del valor del mensaje, dividido por el polinomio (generador), pero se me escapa la implementación real.

He leído Una guía indolora de algoritmos de detección de errores de CRC , y debo decir que no fue indoloro. Repasa bastante bien la teoría, pero el autor nunca llega a un simple "esto es". Dice cuáles son los parámetros para el algoritmo estándar CRC32, pero se olvida de exponer claramente cómo se llega a él.

La parte que me atrapa es cuando dice "esto es todo" y luego agrega, "oh, por cierto, se puede revertir o comenzar con diferentes condiciones iniciales", y no da una respuesta clara de cuál es la forma final de calcular una suma de comprobación CRC32 dados todos los cambios que acaba de agregar.

  • ¿Existe una explicación más sencilla de cómo se calcula CRC32?

Intenté codificar en C cómo se forma la tabla:

for (i = 0; i < 256; i++)
{
    temp = i;

    for (j = 0; j < 8; j++)
    {
        if (temp & 1)
        {
            temp >>= 1;
            temp ^= 0xEDB88320;
        }
        else {temp >>= 1;}
    }
    testcrc[i] = temp;
}

pero esto parece generar valores inconsistentes con valores que he encontrado en otros lugares de Internet. Yo podría utilizar los valores que encontré en línea, pero quiero entender la forma en que fueron creados.

Cualquier ayuda para aclarar estos números increíblemente confusos sería muy apreciada.

aquanar
fuente
9
Su código para generar la tabla CRC32 parece ser correcto. Su polinomio CRC32 lsbit-first ( invertido ) de 0xEDB88320también se puede escribir msbit-first ( normal ) como 0x04C11DB7. ¿Los valores de la tabla que encontró en otro lugar se generaron utilizando el mismo polinomio CRC?
jschmier
1
@jschmier hola, siento que estoy un paso detrás de este chico que hace las preguntas. stackoverflow.com/questions/62168128/…
bluejayke
Si alguien más tiene curiosidad por leer "Una guía indolora para los algoritmos de detección de errores de CRC" vinculada anteriormente, esa URL original está bloqueada, pero Google encontró fácilmente varias copias, incluida esta: zlib.net/crc_v3.txt
Stéphane

Respuestas:

114

El polinomio de CRC32 es:

x 32 + x 26 + x 23 + x 22 + x 16 + x 12 + x 11 + x 10 + x 8 + x 7 + x 5 + x 4 + x 2 + x + 1

O en hexadecimal y binario:

0x 01 04 C1 1D B7
1 0000 0100 1100 0001 0001 1101 1011 0111

El término más alto (x 32 ) generalmente no está escrito explícitamente, por lo que se puede representar en hexadecimal como

0x 04 C1 1D B7

Siéntase libre de contar los 1 y 0, pero encontrará que coinciden con el polinomio, donde 1está el bit 0 (o el primer bit) y el xbit 1 (o el segundo bit).

¿Por qué este polinomio? Porque debe haber un polinomio estándar dado y el estándar fue establecido por IEEE 802.3. Además, es extremadamente difícil encontrar un polinomio que detecte diferentes errores de bits de manera efectiva.

Puede pensar en el CRC-32 como una serie de "Aritmética binaria sin acarreos", o básicamente "operaciones XOR y de desplazamiento". Esto se denomina técnicamente aritmética polinomial.

Para entenderlo mejor, piense en esta multiplicación:

(x^3 + x^2 + x^0)(x^3 + x^1 + x^0)
= (x^6 + x^4 + x^3
 + x^5 + x^3 + x^2
 + x^3 + x^1 + x^0)
= x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0

Si asumimos que x es base 2, obtenemos:

x^7 + x^3 + x^2 + x^1 + x^0

¿Por qué? Debido a que 3x ^ 3 es 11x ^ 11 (pero solo necesitamos 1 o 0 dígitos previos), trasladamos:

=1x^110 + 1x^101 + 1x^100          + 11x^11 + 1x^10 + 1x^1 + x^0
=1x^110 + 1x^101 + 1x^100 + 1x^100 + 1x^11 + 1x^10 + 1x^1 + x^0
=1x^110 + 1x^101 + 1x^101          + 1x^11 + 1x^10 + 1x^1 + x^0
=1x^110 + 1x^110                   + 1x^11 + 1x^10 + 1x^1 + x^0
=1x^111                            + 1x^11 + 1x^10 + 1x^1 + x^0

Pero los matemáticos cambiaron las reglas para que sea el mod 2. Entonces, básicamente, cualquier polinomio binario mod 2 es simplemente una suma sin acarreo ni XOR. Entonces nuestra ecuación original se ve así:

=( 1x^110 + 1x^101 + 1x^100 + 11x^11 + 1x^10 + 1x^1 + x^0 ) MOD 2
=( 1x^110 + 1x^101 + 1x^100 +  1x^11 + 1x^10 + 1x^1 + x^0 )
= x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0 (or that original number we had)

Sé que esto es un acto de fe, pero está más allá de mi capacidad como programador de línea. Si eres un estudiante de informática o un ingeniero empedernido, reto a desglosar esto. Todos se beneficiarán de este análisis.

Entonces, para resolver un ejemplo completo:

   Original message                : 1101011011
   Polynomial of (W)idth 4         :      10011
   Message after appending W zeros : 11010110110000

Ahora dividimos el Mensaje aumentado por el Poli usando aritmética CRC. Esta es la misma división que antes:

            1100001010 = Quotient (nobody cares about the quotient)
       _______________
10011 ) 11010110110000 = Augmented message (1101011011 + 0000)
=Poly   10011,,.,,....
        -----,,.,,....
         10011,.,,....
         10011,.,,....
         -----,.,,....
          00001.,,....
          00000.,,....
          -----.,,....
           00010,,....
           00000,,....
           -----,,....
            00101,....
            00000,....
            -----,....
             01011....
             00000....
             -----....
              10110...
              10011...
              -----...
               01010..
               00000..
               -----..
                10100.
                10011.
                -----.
                 01110
                 00000
                 -----
                  1110 = Remainder = THE CHECKSUM!!!!

La división produce un cociente, que desechamos, y un resto, que es la suma de comprobación calculada. Esto finaliza el cálculo. Por lo general, la suma de verificación se agrega al mensaje y se transmite el resultado. En este caso la transmisión sería: 11010110111110.

Solo use un número de 32 bits como divisor y use toda su transmisión como dividendo. Deseche el cociente y conserve el resto. Marque el resto al final de su mensaje y tendrá un CRC32.

Revisión de chico promedio:

         QUOTIENT
        ----------
DIVISOR ) DIVIDEND
                 = REMAINDER
  1. Toma los primeros 32 bits.
  2. Cambio de bits
  3. Si 32 bits son menores que DIVISOR, vaya al paso 2.
  4. XOR 32 bits por DIVISOR. Vaya al paso 2.

(Tenga en cuenta que el flujo debe poder dividirse en 32 bits o debe rellenarse. Por ejemplo, un flujo ANSI de 8 bits debería rellenarse. También al final del flujo, la división se detiene).

ilkkachu
fuente
13
+1 para la "Revisión de tipo promedio" al final - tal vez considere mover esto a la parte superior - una especie de TL; DR: P
aaronsnoswell
4
@abstractnature Recuerda que dividimos polinomios, no solo números binarios. No podemos hacer una resta "normal" porque no podemos "pedir prestado" $ x ^ n $ de $ x ^ {n + 1} $; son diferentes tipos de cosas. Además, dado que los bits son solo 0 o 1, ¿cuál sería -1? Realmente estamos trabajando en el anillo de polinomios con coeficientes en el campo $ Z / 2Z $, que solo tiene dos elementos, 0 y 1, y donde $ 1 + 1 = 0 $. Al poner los cofcientes en un campo, los polinomios forman lo que se llama un dominio euclidiano, que básicamente permite que lo que estamos tratando de hacer esté bien definido en primer lugar.
calavicci
6
Solo para aclarar que el polinomio real es 100000100110000010001110110110111 = 0x104C11DB7. El MSB está implícito, pero aún debe tenerse en cuenta en una implementación. Como siempre se establecerá porque el polinomio debe tener una longitud de 33 bits (por lo que el resto puede tener una longitud de 32 bits), algunas personas omiten el MSB.
Felipe T.7 de
2
x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0 ... If we assume x is base 2 then we get: x^7 + x^3 + x^2 + x^1 + x^0. No es así como funcionan las matemáticas. Los coeficientes del polinomio son mod (2) o GF (2), las x se dejan solas, lo que da como resultado x ^ 6 + x ^ 5 + x ^ 4 + x ^ 3 + x ^ 2 + x ^ 1 + x ^ 0 (ya que 3 mod (2) = 1). Tack the remainder on the end of your message- técnicamente, el resto se resta de los 0 bits que se agregaron al mensaje, pero como se trata de matemáticas mod (2), tanto la suma como la resta son iguales que XOR, y los bits cero XOR'ed con el resto son iguales como el resto.
rcgldr
2
@MarcusJ - Why did you append four 0s though?- los algoritmos de software para calcular crc agregan efectivamente los 0, aunque no sea evidente. Si se muestra el cálculo de CRC usando la división de mano larga, entonces se deben agregar 0 para que el ejemplo de división aparezca correctamente.
rcgldr
11

Para IEEE802.3, CRC-32. Piense en todo el mensaje como un flujo de bits en serie, agregue 32 ceros al final del mensaje. A continuación, DEBE invertir los bits de CADA byte del mensaje y complementar los primeros 32 bits con un 1. Ahora divida por el polinomio CRC-32, 0x104C11DB7. Finalmente, debe complementar a 1 el resto de 32 bits de esta división, invertir cada uno de los 4 bytes del resto. Esto se convierte en el CRC de 32 bits que se agrega al final del mensaje.

La razón de este extraño procedimiento es que las primeras implementaciones de Ethernet serializarían el mensaje un byte a la vez y transmitirían primero el bit menos significativo de cada byte. El flujo de bits en serie luego pasó por un cálculo de registro de desplazamiento CRC-32 en serie, que simplemente se complementó y se envió por cable después de que se completó el mensaje. La razón para complementar los primeros 32 bits del mensaje es para que no obtenga un CRC completamente cero, incluso si el mensaje fue solo ceros.

Pavlo Bobrek
fuente
2
Esta es la mejor respuesta aquí hasta ahora, aunque reemplazaría 'bit-reverse cada uno de los 4 bytes' por 'bit-reverse los 4 bytes, tratándolos como una entidad', por ejemplo, 'abcdefgh ijklmnop qrstuvwx yzABCDEF' por 'FEDCBAzy xwvutsrq ponmlkji hgfedcba '. Ver también: Tutorial de hash CRC-32 - Comunidad AutoHotkey .
vafylec
1
hola, ¿cuál es el "mensaje" exacto? stackoverflow.com/questions/62168128/…
bluejayke
10

Un CRC es bastante simple; toma un polinomio representado como bits y los datos, y divide el polinomio en los datos (o representa los datos como un polinomio y hace lo mismo). El resto, que está entre 0 y el polinomio es el CRC. Su código es un poco difícil de entender, en parte porque está incompleto: temp y testcrc no están declarados, por lo que no está claro qué se está indexando y cuántos datos se están ejecutando a través del algoritmo.

La forma de entender los CRC es tratar de calcular algunos utilizando un fragmento de datos corto (16 bits más o menos) con un polinomio corto, quizás 4 bits. Si practica de esta manera, realmente comprenderá cómo podría hacerlo para codificarlo.

Si lo hace con frecuencia, un CRC es bastante lento de computar en software. La computación por hardware es mucho más eficiente y requiere solo unas pocas puertas.

Torbellino
fuente
1
Para CRC32 o CRC32b, ¿obtenemos el significado de colisión hash para dos cadenas diferentes? ¿
Obtenemos
1
hola, estoy un poco confundido ¿qué quieres decir con "dividir los polinomios en los datos"? stackoverflow.com/questions/62168128/… ¿Qué es X en el polinomio representado por? ¿Utilizo los bytes oter del fragmento?
bluejayke
7

Además de la verificación de redundancia cíclica de Wikipedia y los artículos de Computación de CRC , encontré un artículo titulado Reversing CRC - Theory and Practice * como una buena referencia.

Básicamente, existen tres enfoques para calcular un CRC: un enfoque algebraico, un enfoque orientado a bits y un enfoque basado en tablas. En Reversing CRC - Theory and Practice * , cada uno de estos tres algoritmos / enfoques se explica en teoría acompañado en el APÉNDICE por una implementación para el CRC32 en el lenguaje de programación C.

* PDF Link
Reversing CRC - Teoría y práctica.
Informe público de HU Berlín
SAR-PR-2006-05 de
mayo de 2006
Autores:
Martin Stigge, Henryk Plötz, Wolf Müller, Jens-Peter Redlich

jschmier
fuente
hola, ¿puedes dar más detalles?
bluejayke
7

Pasé un tiempo tratando de descubrir la respuesta a esta pregunta, y finalmente publiqué un tutorial sobre CRC-32 hoy: Tutorial de hash de CRC-32 - AutoHotkey Community

En este ejemplo, demuestro cómo calcular el hash CRC-32 para la cadena ASCII 'abc':

calculate the CRC-32 hash for the ASCII string 'abc':

inputs:
dividend: binary for 'abc': 0b011000010110001001100011 = 0x616263
polynomial: 0b100000100110000010001110110110111 = 0x104C11DB7

011000010110001001100011
reverse bits in each byte:
100001100100011011000110
append 32 0 bits:
10000110010001101100011000000000000000000000000000000000
XOR the first 4 bytes with 0xFFFFFFFF:
01111001101110010011100111111111000000000000000000000000

'CRC division':
01111001101110010011100111111111000000000000000000000000
 100000100110000010001110110110111
 ---------------------------------
  111000100010010111111010010010110
  100000100110000010001110110110111
  ---------------------------------
   110000001000101011101001001000010
   100000100110000010001110110110111
   ---------------------------------
    100001011101010011001111111101010
    100000100110000010001110110110111
    ---------------------------------
         111101101000100000100101110100000
         100000100110000010001110110110111
         ---------------------------------
          111010011101000101010110000101110
          100000100110000010001110110110111
          ---------------------------------
           110101110110001110110001100110010
           100000100110000010001110110110111
           ---------------------------------
            101010100000011001111110100001010
            100000100110000010001110110110111
            ---------------------------------
              101000011001101111000001011110100
              100000100110000010001110110110111
              ---------------------------------
                100011111110110100111110100001100
                100000100110000010001110110110111
                ---------------------------------
                    110110001101101100000101110110000
                    100000100110000010001110110110111
                    ---------------------------------
                     101101010111011100010110000001110
                     100000100110000010001110110110111
                     ---------------------------------
                       110111000101111001100011011100100
                       100000100110000010001110110110111
                       ---------------------------------
                        10111100011111011101101101010011

remainder: 0b10111100011111011101101101010011 = 0xBC7DDB53
XOR the remainder with 0xFFFFFFFF:
0b01000011100000100010010010101100 = 0x438224AC
reverse bits:
0b00110101001001000100000111000010 = 0x352441C2

thus the CRC-32 hash for the ASCII string 'abc' is 0x352441C2
vafylec
fuente
1
Si desea más velocidad, hubo un método elaborado por algunos ingenieros de Intel en 2006 que utiliza normalmente 4 u 8 bytes del ancho del bus de datos de la máquina simultáneamente. Artículo académico: static.aminer.org/pdf/PDF/000/432/446/… Proyecto en Sourceforge: sourceforge.net/projects/slicing-by-8 Página general de crc: create.stephan-brumme.com/crc32
Alan Corey
1
Hola, gracias, se ve muy bien, pero ¿cómo se obtiene exactamente el valor del polinomio? ¿Qué representa X exactamente? Y cuando dice x ^ 32, ¿es x elevado a 32, o el operador bit a bit ^? stackoverflow.com/questions/62168128/…
bluejayke
1

Para reducir crc32 a aceptar el recordatorio, debe:

  1. Invertir bits en cada byte
  2. xo los primeros cuatro bytes con 0xFF (esto es para evitar errores en los ceros iniciales)
  3. Agregue relleno al final (esto es para que los últimos 4 bytes formen parte del hash)
  4. Calcule el recordatorio
  5. Invierte los bits de nuevo
  6. xor el resultado de nuevo.

En código esto es:


func CRC32 (file []byte) uint32 {
    for i , v := range(file) {
        file[i] = bits.Reverse8(v)
    }
    for i := 0; i < 4; i++ {
        file[i] ^= 0xFF
    }

    // Add padding
    file = append(file, []byte{0, 0, 0, 0}...)
    newReminder := bits.Reverse32(reminderIEEE(file))

    return newReminder ^ 0xFFFFFFFF
}

donde reminderIEEE es el recordatorio puro en GF (2) [x]

Gabriel Furstenheim
fuente
1
Estoy teniendo un poco (juego de palabras) de problemas para entender esto? stackoverflow.com/questions/62168128/…
bluejayke
1
Hola @bluejayke, revisa esta biblioteca github.com/furstenheim/sparse_crc32/blob/master/main.go implementa el crc32 para archivos dispersos, puedes ver allí los detalles esenciales del cálculo. No está optimizado, por lo que es más fácil de seguir que las implementaciones normales. Puede ser que lo que no entiendas es la parte GF (2) [x]. Básicamente x ^ 3 + x significa 1010, x ^ 4 + x + 1 significa 10011. Entonces necesitas realizar una división, por ejemplo x ^ 3 + x es x * (x ^ 2 + 1). por lo que el recordatorio de x ^ 3 + x sobre x es 0, pero sobre x ^ 2 sería x ^ 2 * x + x, es decir, el recordatorio sería x.
Gabriel Furstenheim
1
@bluejayke y reminderIEEE significa recordatorio contra un polinomio bien conocido, el polinomio IEEE
Gabriel Furstenheim
hola de nuevo, gracias por tu respuesta. Solo estoy tratando de entender (para propósitos de javascript) qué representa la "x" en el polinomio. ¿Es "x" algún tipo de palabra clave para algo que me falta aquí? Hay un montón de términos que me confunden aquí, nunca había oído hablar de CRC32 antes, e incluso después de buscar, no pude encontrarlo realmente explicado. Para un PNG, por ejemplo, dice que necesito tomar el "CRC para cada fragmento", ¿eso significa "para todos los datos del fragmento"? Pero, ¿cómo lo "conecto" al polinomio? ¿Qué representa "x"? También cuando dice x ^ 32, es como Math.pow (x, 32) o el bit a bit ^
bluejayke
1
Hola @bluejayke, x es una abstracción para facilitar los cálculos. No se espera que sea sustituido por nada. x ^ 2 Me refiero a x * x, como una multiplicación formal. Aquí chrisballance.com/wp-content/uploads/2015/10/CRC-Primer.html puede encontrar una buena explicación de esa división. Lo que intenté con mi respuesta fue llenar el vacío entre la división (en ese enlace) y el cálculo real
Gabriel Furstenheim