ハッシュ関数は、データから短い指紋のような値を作る道具です。
本記事ではCRC32、MD5、SHA-256の違いを、初心者の方にも伝わるように一歩ずつ説明します。
どれを選ぶべきか、どんな場面に向いているかを具体例とコードで示し、実装時の注意点までまとめます。
安全性と速度のバランスを理解し、正しく使い分けることが大切です。
ハッシュ関数の基本
ハッシュ関数とは
ハッシュ関数は、長さの異なる入力(文字列やファイル)から、決まった長さの出力(ハッシュ値)を計算する関数です。
同じ入力からは必ず同じハッシュ値が得られ、ほんの少しでも入力が違えば大きく違うハッシュ値になります。
ハッシュは暗号化ではなく、元のデータに戻すことはできません。
また、理論上は異なる入力が同じハッシュになる衝突が起こり得ます。
主な用途
ハッシュは日常の多くの場面で使われています。
データの誤り検出、ファイルの整合性確認、重複データの判定、キャッシュのキー生成、署名やメッセージ認証(HMAC)などが代表例です。
誤り検出と改ざん検出は似ていますが目的が異なり、前者は偶発的なエラー、後者は攻撃者の意図的な改ざんを対象にします。
CRCは誤り検出向け、暗号学的ハッシュ(MD5やSHA-256)は改ざん対策向けという違いがあります。
比較の軸
CRC32・MD5・SHA-256を選ぶときは、以下の軸で考えると混乱しにくくなります。
ハッシュ長(ビット数)、計算速度、衝突耐性(安全性)、用途との適合性です。
実装の容易さやライブラリの有無も現実的な判断材料になります。
CRC32・MD5・SHA-256の違い
CRC32とは
CRC32(Cyclic Redundancy Check)は、通信や保存時の偶発的なビット誤りを検出するために設計されたハッシュです。
ネットワークパケットやZIP、PNGなどで広く使われます。
非常に高速で、ランダムエラーの検出性能が高い一方、攻撃者による意図的な改ざんを防ぐ設計ではありません。
CRC32には多くの“流派”(多項式・初期値・反転・最終XOR)があり、実装差で結果が変わることがあります(一般的にはCRC-32(IEEE)が標準的です)。
MD5とは
MD5はかつて広く使われた暗号学的ハッシュですが、現在は衝突が現実的に作られてしまうほど弱点が見つかっています。
セキュリティ目的(改ざん対策や署名)ではMD5を使ってはいけません。
一方で、攻撃者を想定しない用途(重複検出、キャッシュキーなど)では、速度の面から今でも使われることがあります。
SHA-256とは
SHA-256はSHA-2ファミリの一つで、現在も推奨される強力な暗号学的ハッシュです。
TLS(HTTPS)やソフトウェア配布の整合性確認などで広く採用されています。
衝突耐性と広範な実装の両立が特徴で、用途に迷ったらSHA-256が基本の選択肢になります。
ハッシュ長の違い
ハッシュ長は安全性や衝突確率に直結します。
長いほど偶然の衝突は起きにくくなります。
| アルゴリズム | ハッシュ長(ビット) | バイト数 | 16進文字数(小文字) |
|---|---|---|---|
| CRC32 | 32 | 4 | 8 |
| MD5 | 128 | 16 | 32 |
| SHA-256 | 256 | 32 | 64 |
見た目が短いほど扱いやすい反面、衝突のリスクは上がります。
速度と計算コストの違い
おおまかな相対速度は、CRC32が最速、次いでMD5、SHA-256はやや遅いという順です。
とはいえ現代のCPUではSHA-256も十分高速で、多くのアプリケーションでボトルネックにはなりません。
大量のデータをストリーミング処理する場合は差が出やすく、誤り検出ならCRC32、非セキュアな高速用途ならMD5、安全性が必要ならSHA-256を選ぶのが基本です。
衝突耐性と安全性の違い
- CRC32: 偶発的なエラー検出向けで、攻撃への耐性は考慮外です。意図的に同じCRC32を作ることは容易です。
- MD5: 実用的な衝突攻撃が可能で、改ざん対策としては不適切です。
- SHA-256: 現時点で広く安全と見なされ、改ざん検出やHMACに適します。
「改ざん対策」や「認証」が目的なら、素のハッシュではなくHMACや電子署名を使うべき場面が多いことも覚えておくと安全です。
使い分けのガイドライン
通信や保存の誤り検出はCRC32
ネットワークやストレージでは、ランダムなビット反転が主な脅威です。
CRC32はこの種の誤りを効率よく見つけられます。
ZIPやPNGのCRCはまさにその例で、攻撃者を想定しない誤り検出ならCRC32が最適です。
複数のCRC32バリアントがあるため、同じバリアントを使うよう仕様を揃えることが重要です。
配布ファイルの改ざん対策はSHA-256
ソフトウェアやドキュメントを配布する際には、ダウンロード後にSHA-256を計算して公開値と一致するか確認します。
MD5では悪意ある衝突の可能性があり、SHA-256を使うのが安全です。
より強固にするなら、公開サイトはHTTPSで配布し、可能であれば署名(PGPなど)も併用します。
高速な非セキュア用途はMD5
ファイルの重複検出やキャッシュキー生成など、攻撃者がいない前提ならMD5は手軽で高速です。
ただし「改ざん検知」や「本人確認」には使わないでください。
速度をさらに求めるなら、用途に応じてxxHashやBLAKE3などの非暗号学的ハッシュも検討できます(本記事では詳細割愛)。
パスワード保存には使わない
CRC32・MD5・SHA-256をそのままパスワード保存に使ってはいけません。
代わりにbcrypt、scrypt、Argon2などのパスワードハッシュを使用します。
これらはソルトと計算コストを組み合わせ、総当たり攻撃に強くなっています。
SHA-256単体にソルトを足すだけでは十分とは言えません。
使い分けの目安
| 目的 | おすすめ | 理由 | 注意 |
|---|---|---|---|
| 通信/保存の誤り検出 | CRC32 | 高速で偶発エラーに強い | バリアントを統一 |
| ダウンロード整合性 | SHA-256 | 安全性と広い実装 | 可能なら署名も併用 |
| 重複検出/キャッシュ | MD5(非セキュア用途) | 高速・実装容易 | 改ざん対策には不可 |
| メッセージ認証 | HMAC-SHA-256 | 共有鍵で改ざん検知 | 素のSHA-256は不可 |
| パスワード保存 | bcrypt/Argon2等 | 総当たりに強い | ソルトとコスト設定 |
迷ったら「誤り検出=CRC32」「安全な整合性=SHA-256」を基本軸にすると判断しやすくなります。
実装の基本
PythonでのCRC32/MD5/SHA-256
Python標準ライブラリだけで実装できます。
文字列からハッシュを取るときは、まずUTF-8などでバイト列に変換します。
基本の関数
import zlib
import hashlib
def crc32_hex(data: bytes) -> str:
# & 0xffffffff で符号なし32ビットに正規化
return f"{zlib.crc32(data) & 0xffffffff:08x}"
def md5_hex(data: bytes) -> str:
return hashlib.md5(data).hexdigest()
def sha256_hex(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
text = "hello"
b = text.encode("utf-8")
print("CRC32:", crc32_hex(b)) # 8桁16進
print("MD5:", md5_hex(b)) # 32桁16進
print("SHA-256:", sha256_hex(b)) # 64桁16進
大きなファイルを分割してハッシュ
大きなファイルは少しずつ読み込む方がメモリに優しいです。
SHA-256の例を示します。
import hashlib
def sha256_file_hex(path: str, chunk_size: int = 1024 * 1024) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
h.update(chunk)
return h.hexdigest()
print(sha256_file_hex("example.bin"))
テキストモードでは改行コードが変換される可能性があるため、ファイルは必ずバイナリモード(rb)で読みます。
JavaScriptでのMD5/SHA-256
JavaScriptは環境(ブラウザ/Node.js)で手段が少し異なります。
ブラウザでのSHA-256(Web Crypto API)
<script>
async function sha256Hex(text) {
const enc = new TextEncoder();
const data = enc.encode(text);
const buf = await crypto.subtle.digest("SHA-256", data);
const bytes = new Uint8Array(buf);
return Array.from(bytes).map(b => b.toString(16).padStart(2, "0")).join("");
}
sha256Hex("hello").then(console.log);
</script>
WebブラウザにはMD5の標準実装はありません。
どうしてもMD5が必要なら、blueimp-md5などの実績ある外部ライブラリを読み込んで使用します。
<script src="https://cdn.jsdelivr.net/npm/blueimp-md5/js/md5.min.js"></script>
<script>
console.log(md5("hello")); // 32桁16進
</script>
Node.jsでのMD5/SHA-256
const crypto = require("crypto");
function hashHex(alg, input) {
return crypto.createHash(alg).update(input).digest("hex");
}
console.log("MD5:", hashHex("md5", "hello"));
console.log("SHA-256:", hashHex("sha256", "hello"));
GoでのCRC32/MD5/SHA-256
Goは標準パッケージで完結します。
package main
import (
"crypto/md5"
"crypto/sha256"
"fmt"
"hash/crc32"
)
func main() {
data := []byte("hello")
// CRC32(IEEE)
crc := crc32.ChecksumIEEE(data)
fmt.Printf("CRC32: %08x\n", crc)
// MD5
md5Sum := md5.Sum(data)
fmt.Printf("MD5: %x\n", md5Sum)
// SHA-256
shaSum := sha256.Sum256(data)
fmt.Printf("SHA-256: %x\n", shaSum)
}
fmt.Printf(“%x”, …)はバイト列を16進の小文字で出力し、CRC32はゼロ埋め8桁(%08x)でそろえます。
ハッシュの表現形式
ハッシュ値は内部的にはバイト列ですが、人が扱いやすいように文字列表現に変換することが多いです。
- 16進表記(hex): 最も一般的。小文字(例: a1b2…)か大文字(例: A1B2…)を統一します。
- Base64: 文字数を短くでき、URLセーフ版(Base64URL)もあります。
- 生のバイト列: 通信やバイナリ保存に向きます。
| 表現 | 例 | 長所 | 注意 |
|---|---|---|---|
| 16進(小文字) | e3b0c442… | 視認性・比較が容易 | 大小文字の混在を避ける |
| Base64 | 47DEQpj8… | 短くできる | パディング(=)やURLエスケープ |
| バイト列 | 0x… | 高速・省メモリ | 表示には不向き |
同じハッシュでも表現形式が違うと見た目が一致しないため、比較時は形式と大文字/小文字を合わせます。
よくある落とし穴
- 文字コードの違い: “こんにちは”をUTF-8でエンコードするかShift_JISでエンコードするかでバイト列が変わります。常にUTF-8など統一したエンコーディングを使います。
- テキスト/バイナリ読み込み: テキストモードは改行変換が入ることがあるため、ファイルはバイナリモードで読みます。
- CRC32のバリアント: 多項式や初期値が異なると結果が一致しません。仕様で「CRC-32(IEEE)」などバリアントを明記します。
- 先頭ゼロの欠落: CRC32は8桁16進でゼロ埋めします(例: 0000ab12)。言語によってはゼロが落ちるので注意します。
- MD5の誤用: MD5で改ざん対策や署名をしてはいけません。
- 素のハッシュでの認証: 共有鍵があるならHMAC-SHA-256を使用します。素のSHA-256を「署名」として使うのは誤りです。
- パスワード保存: CRC32/MD5/SHA-256単体は不可。bcrypt/Argon2などを使う。ソルトとコストを設定します。
- シリアライズの不一致: JSONのキー順や空白が違うとハッシュも変わります。同じカノニカル形式でシリアライズしてからハッシュ化します。
- 大きなファイルの一括読み込み: メモリを圧迫します。チャンク分割のストリーミングで計算します。
まとめ
CRC32・MD5・SHA-256は、目的と前提が異なるハッシュです。
誤り検出にはCRC32、攻撃者を想定しない高速な用途にはMD5、安全性が必要ならSHA-256という使い分けを押さえれば、初学者の方でも迷いにくくなります。
ハッシュ長は衝突リスクと直結し、実装時はエンコーディングや表現形式、バリアントの統一が重要です。
改ざん検知や認証ではHMAC、パスワード保存ではbcrypt/Argon2など、ハッシュ以外の正しい部品を組み合わせることも欠かせません。
最後に、コード例はあくまで基本形です。
実際のプロダクトではエラー処理や入力の正規化、ストリーミング処理を取り入れ、安全で再現性の高い実装を目指してください。
正しく理解し正しく使えば、ハッシュは強力で信頼できる基盤になります。
