LoginSignup
5
2

More than 1 year has passed since last update.

CRC32の再現方法を求めて

Last updated at Posted at 2022-11-07

巡回冗長検査

巡回冗長検査(Wikipedia)

ファイルの完全性検査などにはSHA256やMD5が使われますが、短いテキストのようなデータの場合、SHA256やMD5は結果が長すぎて使いにくいことがあります。

その点、CRC32は結果が32bitで、16進数表記でも8文字で済むため、元のテキストの代替として使うのに簡便です。

しかし、CRC32と一口に言っても実はいくつかの方式があり、異なる値を算出します。

CRC32の種類

前掲のWikipediaのページを見ても、32bitのCRCには(厳密にはCRCでないAdler32も含めて)

  • CRC-32Adler (厳密にはCRCではないがよく混同される)
  • CRC-32
  • CRC-32C
  • CRC-32K

の4種類が掲載されています。CRC-32Adlerは計算方法が全く異なりますが、それ以外の3つは、Wikipedia記事中では生成多項式と呼んでいる定数が異なるのみです。

手軽なツールの利用(とその罠)

MD5やSHA256などの値は、元の値から完全に決定されるので、オンラインツールが多く存在しており、元の値に機密性がないならこういったツールを利用できます。

そうしてたまたま以下のようなサイト

で生成した crc32 の値を持ってくると、例えば hoge はこのようになります

  • bfotool.com:
    Crc32: 4b775151
    Crc32b: 8b39e45a
    
  • www.paulschou.com:
    crc32: 4b775151
    crc32b: 8b39e45a
    

crc32crc32b があることを気にせずに crc32 を使うこともあると思います。

ただ、同じ計算をほかのサイトでしてみるとちょっとおかしいことに気づきます。

「おかしい」というのは、「(このサイトを使う以外の)ほかの方法で再現方法がわからない」という意味で言っています。後述するようにこの結果は標準化された方法に基づいて算出されており、巡回冗長検査を目的とした結果としては変な結果ではありません。また、このような値を算出するのは(筆者が調べた限り)珍しいかもしれませんが、PHPをバックエンドの言語・ランタイムとして利用していれば自然だといえます。

例えば以下のサイトで計算すると計算結果は crc32b の結果(8b39e45a)になります。

また、例えばJavaの CRC32 や Goの hash/crc32 パッケージを使って計算しても同じ結果になります。

ちなみにGoの場合は、Wikipediaにあった CRC-32Adler (hash/adler32パッケージを利用))、CRC-32CCRC-32K のすべてが実装されていますがいずれで計算しても上記 crc32 の結果とは一致しません)

では先ほどの crc32 の結果は何だったのでしょうか?

深まる謎

crc32crc32b で調べると、どうもPHPに hash という関数があり、その引数としてハッシュアルゴリズムを指定するときの引数で crc32/crc32b という指定があるようです。

気づくのが遅かったのは筆者がPHP経験ゼロのためです

そしてこれらの違いは利用者にとっても不思議なもののようで、StackOverflowにも質問されていました。

ただ、回答は「それぞれ別のアルゴリズムだよ。詳細はそれぞれの標準を見に行ってね。」(意訳)といったもので、実用上はそれで困らないものの、何がどう違うのか(例えば生成多項式がどう違うのか)といったことは分かりませんでした。

この時点でPHPを使えば結果を再現できることは分かったものの、PHP経験ゼロの筆者としては「自分の使える道具立てで再現したい」と考え、調査を続行しました。

ヒント

ヒントになったのは以下のサイトの計算結果でした。

CRC-32 という結果のほかに CRC-32/BZIP2 という行があり、入力 hoge に対して 0x5151774B という結果が出ています。バイト順序こそ逆ですが 4b775151 が出力されていたのです。

このサイトはアルゴリズムの違ういろいろなCRC32を計算してくれており、その詳細についても表形式でまとまっています。

CRC-32と CRC-32/BZIP2を取り出してみると(resultなどの列は入力 hoge に対するもの)

Algorithm Result Check Poly Init RefIn RefOut XorOut
CRC-32 0x8B39E45A 0xCBF43926 0x04C11DB7 0xFFFFFFFF true true 0xFFFFFFFF
CRC-32/BZIP2 0x5151774B 0xFC891918 0x04C11DB7 0xFFFFFFFF false false 0xFFFFFFFF

となっていて、RefIn/RefOut が異なるのみのようです。

解決編

眠い目をこすりながら crc refin で検索して次のページにたどり着きました。

Google翻訳を使いながら読んでいくと、どうもGitHubに具体的なコードがあるようです。

crc32crc32_mpeg_2 という関数が上記コードの最下部にあり、これらがそれぞれPHPの crc32crc32b (のバイト順序が逆のもの)に相当するようです。

当該Webサイトがアクセスできなくなったあとも結果を再現できれば良い(←今決めた)のでGoでコードを書き直しておきます。

func crc32(b []byte) uint32 {
	var crc uint32 = 0xffffffff
	for _, c := range in {
		crc = crc ^ ((uint32)(c) << 24)
		for i := 0; i < 8; i++ {
			if crc & 0x80000000 != 0 {
				crc = (crc << 1) ^ 0x04C11DB7
			} else {
				crc = (crc << 1)
			}
		}
	}
	return 0xffffffff ^ crc
}
func crc32b(in []byte) uint32 {
	var crc uint32 = 0xffffffff
	for _, c := range in {
		crc = crc ^ (uint32)(c)
		for i := 0; i < 8; i++ {
			if crc & 0x1 != 0 {
				crc = (crc >> 1) ^ 0xEDB88320
			} else {
				crc = (crc >> 1)
			}
		} 
	}
	return 0xffffffff ^ crc
}

基本はこれで crc32(...) を使えばPHPの hash("crc32", ...) 相当、 crc32b(...) を使えば hash("crc32b", ...) 相当の結果が得られます。

なお、前述の通りPHPの crc32 の結果はバイト順序が逆になっているので、これを逆にするには encoding/binary パッケージのバイト順序関数を使って

import (
    "encoding/binary"
    "fmt"
)

func main() {
    var b []byte = ([]byte)("hoge") // 入力
    // PHPのhash("crc32", ...) 関数に合わせるためにLittleEndianを使ってバイト順序を逆にしている
    fmt.Printf("crc32: %08X\n", binary.LittleEndian.AppendUvarint([]byte{}, crc32(b))
}

のようにすればよい。もしバイト配列ではなく uint32 が欲しければ binary.BigEndian.Uint32([]byte) uint32 で戻せばよい(この時はBigEndianにしておかないと順序が戻ってしまう)。

最後に

巡回冗長検査というのは抽象的な方式に名前が付けられているだけで、具体的なアルゴリズムではないことが分かった。

また、PHPのhash関数のような変種についてWikipediaにも

誤り検出符号としてのCRCの概念を実用システムでの実装に移すとき、実装者がそれを複雑化させることがある。以下では、そのような例を解説する。

(中略)

  • ビット順序: ある種の方式では各バイトの最下位ビットを先頭とする。すると多項式除算での「左端」は通常の意味での最下位ビットになる。これはシリアルポートでの転送でハードウェアによるCRCチェックを行う場合に良く見られる。というのも、シリアルポートでは最下位ビットを先に転送するものが多いためである。
  • バイト順序: 多バイトCRCでは、バイトの転送順序に混乱が見られる。一部の16ビットCRCではCRCを構成する2バイトを入れ替えている。

巡回冗長検査 - Wikipediaより

という形で記載があった。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2