CRC-32

  七誌
経緯
1.       ZIPの実装でCRC-32が必要になった
2.       そもそもCRCって何だろう?
3.       Wikipediaで何やら説明されている
4.       読んだけど意味がよくわからない
     •    CRC-32の規格が色々あるけどどれ?
     •    0x04c11db7とかの定数は何?
     •    ZIP仕様書の0xdebb20e3との関係は?
5. というわけで調べてみた
結論から言うと
• ZIPで使われているCRC-32はIEEE 802.3




• CRCの計算は特殊な割り算の余り
• マジックナンバーは検算用の値
目次

1.   計算編   ―   Wikipediaの解説を追試
2.   理論編   ―   計算方法の根拠
3.   手順編   ―   CRC-32の計算手順
4.   実装編   ―   実装に必要なテクニックなど
5.   番外編   ―   マジックナンバー
1. 計算編
Wikipediaの解説を追試
手探り
• どこから手を付ければ良いんだろう?
• とりあえずWikipediaに書いてある計算をやっ
  てみよう
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  11010011101100
  1011
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  11010011101100
  1011
  0110
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  01100011101100
  1011
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  01100011101100
   1011
    0111
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  00111011101100
   1011
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  00111011101100
    1011
    0101
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  00010111101100
    1011
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  00010111101100
     1011
     0000
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  00000001101100
     1011
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  00000001101100
         1011
         0110
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  00000000110100
         1011
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  00000000110100
          1011
          0110
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  00000000011000
          1011
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  00000000011000
           1011
           0111
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  00000000001110
           1011
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す


  00000000001110
            1011
            0101
CRCの計算
1. 先頭が一致ならXOR
2. 右シフト
左端まで1~2を繰り返す
                求める値

  00000000000101
            1011
まとめ


              0x34ec CRC-3
                上位1bitを付加

• 0x34ecに対するCRC-3の値
• Wikipediaの一覧表にCRC-3はない
• 計算例として仮に出しただけ?
2. 理論編
  CRCの計算方法は
どのような根拠に基づくのか
modulo 2
• 計算例で求めた値はmodulo 2の剰余
 – 0x34ec mod2 0xb = 5
• modulo 2とは、繰り上がり・繰り下がりを無視
  して各桁を独立に計算する方法
• 足し算・引き算・XORが同一の値
 – 0+0=0-0=0^0=0 0+1=0-1=0^1=1
 – 1+0=1-0=1^0=1 1+1=1-1=1^1=0
 – 実装では加算・減算の代用にXORを使う
• つまりCRCとは特殊な割り算の余り
割り算(普通)
• 10進数の割り算は掛け算が必要
• 2進数は0と1しかないため掛け算が不要
        28                11100
    35 987    100011 1111011011
             除数まま→   100011
 掛け算→  70
              引き算→    110101
 引き算→  287   除数まま→    100011
 掛け算→  280    引き算→     100100
             除数まま→     100011
 引き算→    7
              引き算→          111
割り算(modulo 2)
• 引き算の代わりに排他的論理和(XOR)
            11100               11111
100011 1111011011   100011 1111011011
       100011              100011
引き算→    110101       XOR→   111101
        100011              100011
引き算→     100100      XOR→    111100
         100011              100011
引き算→          111    XOR→     111111
                              100011
                     XOR→      111001
                               100011
                     XOR→       11010
なぜmodulo 2?
• CRCはデータチェックが目的
 – 割り算をすること自体に意味はない
• データの破損が検出できれば何でも良い
 – 通常の算数と違っても問題はない
• 計算が簡単なmodulo 2を採用
 – 引き算は繰り下がりで他の桁に影響
 – XORは他の桁に影響を及ぼさない
CRC-32
• 除数0x104c11db7によるmodulo 2剰余
• バイト配列を巨大数に見立てて割る
• 前処理・後処理で検出力向上
 – 前処理(被除数)→計算→後処理(剰余)
 – modulo 2除算自体は素直に計算
• サンプル実装は最適化されている
 – 原理と実装のつながりが分かりにくい
 – 最適化を考慮して仕様が決められた?
3. 手順編
CRC-32の計算手順
被除数の作成 (1)
• バイトごとにビット順序を反転
 – “a” → 01100001 → 10000110 → 0x86
 – “b” → 01100010 → 01000110 → 0x46
 – “ab” → 0x86 0x46
 – “abcd” → 0x86 0x46 0xc6 0x26
• 計算のハードウェア処理を考慮?
 – シリアルポートのプロトコルとも関係?
 – Wikipediaにそのようなことが書いてある
被除数の作成 (2)
• 4バイトの0を後置
 – “a” → 0x86 0x00 0x00 0x00 0x00
• ビッグエンディアンとして数値化
 – “a” → 0x8600000000
 – “abcd” → 0x8646c62600000000
• 被除数を除数より大きくするための処置
 – 被除数<除数 のとき 被除数=剰余
 – 被除数がそのまま剰余になるのを回避
前処理(被除数)
• そのままでは直前の0が無視される
 – “a” → 0x8600000000
 – “¥0¥0a” → 0x00008600000000
 – 0の個数の誤りが検出不可能
• 対策として上位4バイトのビット値を反転
 – “a” → 0x8600000000 → 0x79ffffff00
 – “¥0¥0a” → 0x00008600000000
   → 0xffff79ff000000
• ff×4に続く00の個数は検知不能
 – レアケースのため無視?
除数(1)
• Wikipediaにある多項式は除数
• x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+
  x4+x2+x+1
• x = 2 → 232+226+223+222+・・・+22+21+20
  – 2進数でビットが立つ位置を表す式
• 100000100110000010001110110110111
• 0x104c11db7 (=CRC-32の除数)
• CRCの種類ごとに除数は異なる
除数(2)
•  Wikipediaで多項式の横にある3つの値は、
   計算用に除数から派生した値
1. 除数から最上位ビットを落とした値
    – 0x104c11db7 → 0x04c11db7
2. 1のビット順序を反転した値
    – 0x04c11db7 → 0xedb88320
3. 除数を1ビット右シフトした値
    – 0x104c11db7 >> 1 → 0x82608edb
•   1と2は実装編で説明、3は用途不明
modulo 2剰余
• 除数0x104c11db7でmodulo 2剰余
 – “a” → 0x79ffffff00 mod2 0x104c11db7
   → 0x3d8212e8
 – “abcd”
   → 0x79b939d900000000 mod2 0x104c11db7
   → 0x774cbe48
• 計算方法のカスタマイズはない
 – 実装上の都合により計算方法は変形して実装さ
   れるが(後述)、計算結果は同一
後処理(剰余)
• 剰余のビット順序を反転
 – “a” → 0x3d8212e8 → 0x174841bc
 – “abcd” → 0x774cbe48 → 0x127d32ee
 – 入力をバイトごとに反転した反動?
• ビット値を反転してCRC-32の値とする
 – “a” → 0x174841bc → 0xe8b7be43
 – “abcd” → 0x127d32ee → 0xed82cd11
 – 前処理で上位4バイトを反転した反動?
• 反転によりマジックナンバー出現(後述)
実装例(F#)
• ビット順序を反転する関数を定義
let rev (v:uint32) =
    let mutable ret = 0u
    for i = 0 to 31 do
        if v &&& (1u <<< i) <> 0u then
            ret <- ret ||| (1u <<< (31 - i))
    ret

printfn "%x" (rev 1u) // 80000000


• 破壊的代入に突っ込まないでください・・・
実装例(F#)
• データが1バイトの場合
let crc32_1 (b:byte) =
    let b' = uint32 b
    let mutable 被除数 = uint64(~~~(rev b')) <<< 8
    let 除数    = 0x104c11db7UL
    let 最上位 = 0x100000000UL
    for i = 7 downto 0 do
        if 被除数 &&& (最上位 <<< i) <> 0UL then
            被除数 <- 被除数 ^^^ (除数 <<< i)
    rev(~~~(uint32 被除数))

printf "%x" (crc32_1(byte 'a'))
// e8b7be43
実装例(F#)
• データが4バイトの場合
let crc32_4 (buf:byte[]) =
    let buf' = BitConverter.ToUInt32(buf, 0)
    let mutable 被除数 = uint64(~~~(rev buf')) <<< 32
    let 除数    = 0x104c11db7UL
    let 最上位 = 0x100000000UL
    for i = 31 downto 0 do
        if 被除数 &&& (最上位 <<< i) <> 0UL then
            被除数 <- 被除数 ^^^ (除数 <<< i)
    rev(~~~(uint32 被除数))

let bytes = Encoding.ASCII.GetBytes "abcd"
printf "%x" (crc32_4 bytes)
// ed82cd11
検算(Python)
• 既存実装としてPythonと比較
>>> from struct import pack
>>> from binascii import crc32, hexlify
>>> hexlify(pack(">i", crc32("a")))
'e8b7be43'
>>> hexlify(pack(">i", crc32("abcd")))
'ed82cd11'

• F#の独自実装で求めた値と一致!
 – “a” → 0xe8b7be43
 – “abcd” → 0xed82cd11
まとめ
1.       入力
     •     “abcd” → 0x61 0x62 0x63 0x64
2.       バイトごとにビット順序を反転
     •     0x86 0x46 0xc6 0x26
3.       4バイトの0を後置してビッグエンディアンとして数値化
     •     0x8646c62600000000
4.       上位4バイトのビット値を反転 (ここまで前処理)
     •     0x79b939d900000000
5.       除数0x104c11db7でmodulo 2の剰余を計算
     •     0x774cbe48
6.       剰余のビット順序を反転 (ここから後処理)
     •     0x127d32ee
7.       ビット値を反転してCRC-32の値とする
     •     0xed82cd11
4. 実装編
実装に必要なテクニックなど
巨大数
• 巨大なデータをそのまま数値化して被除数と
  して扱うのは困難
 – 手順編の例で、“abcd”のように被除数が64bitに
   収まるようにしたのはそのため
• 除数は33bitだが、32bitで扱えないか?
 – たった1bitはみ出しただけなのに・・・
• 実装に際して何らかの工夫が必要
 – 既存の実装は色々あるのに、工夫の意味を理解
   しないとまともに読めない!
シフトの相対性
• 計算編では除数をシフト
 – 被除数   ←11010011101100
 – 除数    ←1011→
• 除数を固定して被除数をシフトしても、計算結
  果は変わらない
 – 被除数   ←11010011101100
 – 除数    ←1011
• 計算に関係するのはあくまで相対位置
実装例(F#)
let crc32_4 (buf:byte[]) =
                                              除数をシフト
    let buf' = BitConverter.ToUInt32(buf, 0)
    let mutable 被除数 = uint64(~~~(rev buf')) <<< 32
    let 除数    = 0x104c11db7UL
    let 最上位 = 0x100000000UL
    for i = 31 downto 0 do
        if 被除数 &&& (最上位 <<< i) <> 0UL then
            被除数 <- 被除数 ^^^ (除数 <<< i)
    rev(~~~(uint32 被除数))

let crc32_4 (buf:byte[]) =
                                             被除数をシフト
    let buf' = BitConverter.ToUInt32(buf, 0)
    let mutable 被除数 = uint64(~~~(rev buf'))
    let 除数    = 0x104c11db7UL
    let 最上位 = 0x100000000UL
    for i = 0 to 31 do
        被除数 <- (if 被除数 &&& 最上位 = 0UL
                  then 被除数 else (被除数 ^^^ 除数)) <<< 1
    rev(~~~(uint32 被除数))
最上位ビット
• 被除数の最上位ビットはXORで0になる
 – 被除数    ←11010011101100
 – 除数     ←1011
• 被除数の最上位ビットは値だけ見て、シフトで
  押し出して捨ててしまっても構わない
 – どうせ捨てるので、除数とXORする必要はない
 – XORしないなら、除数から最上位ビットを取り除
   いても計算結果に影響しない
• CRC-32の除数が1bit減って32bitに収まる
実装例(F#)
let crc32_4 (buf:byte[]) =
                                             除数が33bit
    let buf' = BitConverter.ToUInt32(buf, 0)
    let mutable 被除数 = uint64(~~~(rev buf'))
    let 除数    = 0x104c11db7UL
    let 最上位 = 0x100000000UL
    for i = 0 to 31 do
        被除数 <- (if 被除数 &&& 最上位 = 0UL
                  then 被除数 else (被除数 ^^^ 除数)) <<< 1
    rev(~~~(uint32 被除数))

let crc32_4 (buf:byte[]) =
                                             計算を32bit化
    let buf' = BitConverter.ToUInt32(buf, 0)
    let mutable 被除数 = ~~~(rev buf')
    let 除数    = 0x04c11db7u // Wikipediaに出てきた値(標準)
    let 最上位 = 0x80000000u
    for i = 0 to 31 do
        let 被除数' = 被除数 <<< 1
        被除数 <- if 被除数 &&& 最上位 = 0u
                 then 被除数' else 被除数' ^^^ 除数
    rev(~~~被除数)
ビット順序の反転(1)
• 計算を反転させるとどうなるか?
• 標準
 – 被除数   ←11010011101100
 – 除数    ←1011
• 反転
 – 被除数   ←00110111001011→
 – 除数    ←          1101
• 最終的に得られた結果を反転すれば、同じ値
  が得られる
ビット順序の反転(2)
• 元はコードのあちこちで反転させていた
• 計算自体を反転させると、反転が相殺して消
  え、コードが単純化になる
 – データをバイトごとに反転させる必要がなくなる
• 除数は反転した値を用意して使う
 – Wikipediaに出てきた0xedb88320
• これを想定して規格が決められた?
 – いずれにしても計算は単純な方が良い
実装例(F#)
let crc32_4 (buf:byte[]) =
                                                 標準
    let buf' = BitConverter.ToUInt32(buf, 0)
    let mutable 被除数 = ~~~(rev buf')
    let 除数    = 0x04c11db7u // Wikipediaに出てきた値(標準)
    let 最上位 = 0x80000000u
    for i = 0 to 31 do
        let 被除数' = 被除数 <<< 1
        被除数 <- if 被除数 &&& 最上位 = 0u
                 then 被除数' else 被除数' ^^^ 除数
    rev(~~~被除数)

let crc32_4 (buf:byte[]) =
                                                反転
    let buf' = BitConverter.ToUInt32(buf, 0)
    let mutable 被除数 = ~~~buf'
    let 除数 = 0xedb88320u // Wikipediaに出てきた値(反転)
    for i = 0 to 31 do
        let 被除数' = 被除数 >>> 1
        被除数 <- if 被除数 &&& 1u = 0u
                 then 被除数' else 被除数' ^^^ 除数
    ~~~被除数
データ逐次投入(1)
被除数を一気に作成(標準)                        被除数を一気に作成(反転)
1.       データ                         1.   データ
     –    “abcde”                         –   “abcde”
2.       16進数                        2.   16進数
     –    0x61 0x62 0x63 0x64 0x65        –   0x61 0x62 0x63 0x64 0x65
3.       ビット順序反転                     3.   バイト順序反転
     –    0x86 0x46 0xc6 0x26 0xa6        –   0x65 0x64 0x63 0x62 0x61
4.   数値化                             4.  数値化
   –   0x8646c626a600000000             – 0x6564636261
5.   4バイトビット値反転                      5. 4バイトビット値反転
   –   0x79b939d9a600000000             – 0x659b9c9d9e
データ逐次投入(2)
被除数を一気に作成(反転)                       被除数を逐次投入で作成
1.   データ                            1. 初期値(4バイト反転込み)
     –   “abcde”                       – ffffffff
2.   16進数                           2. 下位から1文字ずつXOR
     –   0x61 0x62 0x63 0x64 0x65      –      ffffffff
3.   バイト順序反転                             ^             61
     –   0x65 0x64 0x63 0x62 0x61        ^          62
4.  数値化                                  ^       63
                                         ^    64
   – 0x6564636261
                                         ^ 65
5. 4バイトビット値反転
                                    3. 結果
   – 0x659b9c9d9e
                                       – 0x659b9c9d9e
データ逐次投入(3)
• CRC計算との関係
 – 初期値     ffffffff→
 – データ           61→
               62→
             63→
           64→
         66→
 – 除数      edb88320
• これらをXORで重ねて計算を進める
データ逐次投入(4)
• データを逐次投入しながら計   初期値 ffffffff→
                  データ
  算を進めても、同一の結果が             61→
                  除数  edb88320
  得られる                  ffffff→
• 交換法則による計算順序の              62→
  入れ替え                edb88320
                          ffff→
• XORは交換法則が成り立つ
                            63→
• 逐次投入により、任意の長さ       edb88320
  のデータ処理が可能                 ff→
                            64→
                      edb88320
                            65→
                      edb88320
実装例(F#)
let crc32_4 (buf:byte[]) =
                                             一気に用意
    let buf' = BitConverter.ToUInt32(buf, 0)
    let mutable 被除数 = ~~~buf'
    let 除数 = 0xedb88320u
    for i = 0 to 31 do
        let 被除数' = 被除数 >>> 1
        被除数 <- if 被除数 &&& 1u = 0u
                 then 被除数' else 被除数' ^^^ 除数
    ~~~被除数

let crc32 (buf:byte[]) =
                                    逐次投入により任意長対応
    let mutable 被除数 = ~~~0u
    let 除数 = 0xedb88320u
    for b in buf do
        被除数 <- 被除数 ^^^ uint32(b)
        for j = 0 to 7 do
            let 被除数' = 被除数 >>> 1
            被除数 <- if 被除数 &&& 1u = 0u
                     then 被除数' else 被除数' ^^^ 除数
    ~~~被除数
テーブル
• ビット単位で計算すると無駄が多い
• テーブルを用いてバイト単位で計算
 – 1バイト(0~255)のすべての計算パターンを事
   前にキャッシュしておく
• XORの交換法則により、あらかじめ計算した
  値を後で重ねても、同一の結果となる
• CRCのサンプル実装として出回っているコー
  ドの大半はテーブルを用いている
実装例(F#)
let crc32_table =
    [| for i in 0..255 ->
        let mutable reg = uint32 i
        for j = 0 to 7 do
            let reg' = reg >>> 1
            reg <- if reg &&& 1u = 0u
                   then reg' else reg' ^^^ 0xedb88320u
        reg |]

let crc32 (buf:byte[]) =
    let mutable reg = ~~~0u
    for b in buf do
        reg <- reg ^^^ (uint32 b)
        let t = crc32_table.[int(reg) &&& 0xff]
        reg <- (reg >>> 8) ^^^ t
    ~~~reg

• 大抵のサンプル実装は似たようなコード
5. 番外編
CRCの特性によるマジックナンバー
マジックナンバー(1)
1. データのCRCを計算
2. データの後に1で計算したCRCを追加
3. 2のCRCを計算
 – データの内容に関係なく常に同じ値になる
 – この性質を検算に利用することがある

    データ    CRC   CRC

          常に同じ値=マジックナンバー
マジックナンバー(2)
• CRC-32で確認、見事に一致!
 – “a” → 0xe8b7be43 → 0x2144df1c
 – “abcd” → 0xed82cd11 → 0x2144df1c
 – XORによる相殺の結果
• CRCは最後にビット値を反転するが、反転す
  る前の0xdebb20e3がZIP仕様書に記載され
  ているマジックナンバー
 – ~0xdebb20e3 → 0x2144df1c
• 後処理で反転する目的がこれ?
検算(Python)
>>> from struct import pack
>>> from binascii import crc32
>>> def magic(data1):
...     crc1 = crc32(data1)
...     data2 = data1 + pack("<i", crc1)
...     return crc32(data2)
...
>>> hex(magic("a"))
'0x2144df1c'
>>> hex(magic("abcd"))
'0x2144df1c'
>>> hex(magic("abcdefghijklmn"))
'0x2144df1c'
ご清聴ありがとうございました

CRC-32