ハッシュ関数解説:MD5、SHA-256、それぞれの使い分け

9 min2026年5月18日

ハッシュ関数とは何か(MD5 vs SHA-256を平易に)

ハッシュ関数は任意の入力(1文字でも10 GBのファイルでも空文字列でも)を受け取り、固定サイズの出力(ダイジェスト)を生成します。MD5は常に128ビット(16進数32文字)を出力し、SHA-256は常に256ビット(16進数64文字)を出力します。同じ入力は常に同じ出力を生成しますが、出力から入力を復元することはできません。これがハッシュ関数の全てです。

MD5 vs SHA-256の議論は一つの質問に帰着します:衝突耐性が必要かどうか。衝突耐性とは、同じハッシュを生成する2つの異なる入力を計算的に見つけることが不可能であるべきという性質です。MD5は2004年にXiaoyun Wangが実用的な衝突攻撃を実証して以来、この性質を失っています。2012年には研究者が異なる表示内容を持つが同一のMD5ハッシュを持つPDFを作成できるようになりました。SHA-256には既知の衝突がなく、今後数十年は安全であると期待されています。

しかし多くの記事が間違っている点があります:MD5はすべてにおいて「壊れている」わけではありません。セキュリティ用途(デジタル署名、証明書、悪意ある改ざんに対する完全性検証)には壊れています。非セキュリティ用途(重複排除、キャッシュキー、偶発的な破損に対するチェックサム、ハッシュテーブルの分散)には完全に問題ありません。脅威モデルに意図的な衝突の作成を含む攻撃者がいない場合、MD5は高速で十分です。

ハッシュ関数の動作メカニズム

すべての暗号学的ハッシュ関数は同じパターンに従います:入力をブロックサイズの倍数にパディングし、ブロックに分割し、各ブロックを圧縮関数で処理して実行状態と混合します。MD5は512ビットブロックを使い4ラウンド×16操作を行います。SHA-256は512ビットブロックを使い64ラウンドを行います。ラウンド数が多い=混合が多い=逆算や衝突発見が困難になります。

アバランシェ効果はハッシュを有用にする特性です:入力の1ビットを変更すると、出力ビットの約50%が反転します。「hello」と「hellp」は全く異なるハッシュを生成し、目に見える関係がありません。入力について出力から何も推測できず、似た入力が似た出力を生成することもありません。当サイトのhash-generatorツールでこれを確認できます — 1文字変えるとハッシュ全体が変わります。

性能は劇的に異なります。モダンなハードウェア(Intel i7-13700K)で:MD5は約6 GB/s、SHA-256は約2 GB/s(SHA-NIハードウェアアクセラレーション利用時は8 GB/s)、SHA-3は約1.5 GB/s、BLAKE3は約12 GB/s(SIMD向けに設計されている)。パスワードのハッシュ化には遅さが必要です — bcrypt(cost 12)は同じCPUで毎秒約4ハッシュしか処理しません。これは意図的です。

見落とされがちな点:ハッシュ関数は決定的ですがエンコーディング間で移植可能ではありません。文字列「hello」のSHA-256はUTF-8、UTF-16、ASCIIのどれでエンコードするかによって異なります。バイト列が異なるのでハッシュも異なります。常にエンコーディングを指定してください。JavaScriptではnew TextEncoder().encode("hello")でUTF-8バイトが得られ、これが標準的な慣例です。

// 同じ文字列、異なるエンコーディング = 異なるハッシュ
const text = "hello";

// UTF-8(標準): 68 65 6c 6c 6f(5バイト)
// SHA-256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

// UTF-16LE: 68 00 65 00 6c 00 6c 00 6f 00(10バイト)
// SHA-256: 全く異なるハッシュ

// 特別な理由がない限り常にUTF-8を使う
const encoder = new TextEncoder(); // デフォルトでUTF-8
const bytes = encoder.encode(text);
const hash = await crypto.subtle.digest("SHA-256", bytes);

用途別アルゴリズム選択ガイド

ファイルの完全性検証(偶発的な破損検出):MD5またはCRC32で十分です。ディスクエラーやネットワーク障害からの保護であり、攻撃者からの保護ではありません。MD5はSHA-256より高速で、128ビットの出力はこの目的に十分です。Linuxのパッケージマネージャーは後方互換性のためにSHA-256と並行してMD5チェックサムを使い続けています。

コンテンツアドレッシングと重複排除:SHA-256が標準です。GitはSHA-1(SHA-256に移行中)、IPFSはSHA-256、DockerイメージレイヤーはSHA-256を使います。ハッシュがコンテンツのアイデンティティになります。2つのファイルが同じSHA-256を持てば、同一ファイルです(圧倒的な確率で)。MD5はここでは使わないでください — 攻撃者が正規ファイルと同じMD5を持つ悪意あるファイルを作成できるためです。

デジタル署名と証明書:最低SHA-256。TLS証明書は2017年にGoogleがSHA-1衝突を実証(「SHAttered」攻撃、GPUクラウド時間で約$110,000)した後、SHA-1からSHA-256に移行しました。新しいシステムではSHA-256またはSHA-3が良い選択です。Ed25519署名は内部でSHA-512を使用しています。

パスワードハッシュ:上記のどれも使わないでください。MD5、SHA-256、SHA-3はすべてパスワードには不適切です。速すぎるからです。bcrypt、scrypt、またはArgon2idを使ってください。GPUは毎秒100億のSHA-256ハッシュを計算できますが、bcryptは毎秒約70ハッシュだけです。当サイトのpassword-generatorツールで、遅いハッシュのブルートフォースにも耐えるパスワードを生成できます。

SHAファミリー:SHA-1、SHA-2、SHA-3

SHA-1(160ビット):2017年以降壊れています。GoogleのSHAttered攻撃で、同一SHA-1ハッシュを持つ2つの異なるPDFが生成されました。コスト:2017年のクラウドGPU時間で約$110,000、現在はおそらく$10,000以下。セキュリティ関連では一切使わないでください。Gitは依然使用していますがSHA-256に移行中です。Chrome、Firefoxは2017年からSHA-1証明書を拒否しています。

SHA-2ファミリー(SHA-224、SHA-256、SHA-384、SHA-512):現在の標準です。SHA-256が最も一般的なバリアントです。SHA-512は64ビットプロセッサ上でSHA-256より高速です(直感に反しますが、1024ビットブロックを処理し、64ビット演算がネイティブなため)。SHA-384はSHA-512と異なる初期状態で出力を切り詰めたものです。ほとんどの目的にはSHA-256がデフォルトの選択です。

SHA-3(Keccak、2015年に標準化):SHA-2とは完全に異なる内部設計(Merkle-DamgårdではなくSponge構造)です。SHA-2が破られた場合のバックアップとして存在します。無関係な2つのアルゴリズムを持つことで、一方への突破がもう一方に影響しません。SHA-3はソフトウェアではSHA-2よりやや遅いですが、セキュリティマージンが広いです。コンプライアンス要件で指定されている場合に使い、それ以外ではSHA-256で問題ありません。

BLAKE3(2020年):SHA系列ではありませんが言及に値します。SHA-256の3〜6倍高速で、並列化可能(CPUコアでスケール)で、256ビット出力です。Bao(検証付きストリーミング)、Rustエコシステム、コンテンツアドレスストレージで使用が増えています。欠点:比較的新しくNIST標準にはまだ含まれないため、規制産業では使えません。内部ツールや性能重視のアプリケーションにはBLAKE3は優秀です。

ハッシュ衝突:実際の意味

衝突とは、同じハッシュ出力を生成する2つの異なる入力のことです。128ビットハッシュ(MD5)の場合、誕生日のパラドックスにより約2^64回(約1800京回)の試行で衝突が見つかる確率が高くなります。SHA-256(256ビット)では2^128回の試行が必要で、これは観測可能な宇宙の原子数より多い数です。実際にはSHA-256の衝突がブルートフォースで見つかることは永遠にありません。

しかしブルートフォースだけが攻撃ではありません。暗号解析はアルゴリズムの数学的弱点を突きます。MD5の圧縮関数には構造的欠陥があり、ラップトップ上で秒単位で衝突を発見できます(2^64回ではなく約2^18回、数十万回の操作)。SHA-1は約2^63回の操作を要します(依然として高コストですが、国家レベルや潤沢な資金を持つ攻撃者には実行可能)。

衝突で攻撃者は何ができるか:同じハッシュを持つ2つの文書を作成できます。1つは無害、もう1つは悪意あるもの。無害な方に署名/認証を取得し、悪意ある方に差し替えます。Flameマルウェア(2012年)はMD5衝突を使ってMicrosoft Windows Updateの証明書を偽造しました。認証局が正当に見える証明書に署名しましたが、攻撃者はマルウェア用に機能する衝突する証明書を持っていたのです。

非セキュリティ用途では衝突は問題になりません。MD5をハッシュテーブルのキーやキャッシュ識別子として使う場合、衝突は単に2つの異なる入力が同じバケットにマッピングされることを意味し、チェイニングやオープンアドレッシングで処理されます。ランダムな入力での確率は極めて低く(1/2^64)、実務で遭遇することはありません。セキュリティの懸念は意図的に作成された衝突に限定されます。

HMAC:ハッシュだけでは不十分な場面

単純なハッシュは完全性(データが偶発的に破損していない)を検証しますが、真正性(データが想定する送信者からのもの)は検証しません。データを改変した攻撃者はハッシュを再計算できます。HMAC(Hash-based Message Authentication Code)は秘密鍵をハッシュに混入することでこれを解決します:HMAC(key, message) = Hash((key ⊕ opad) || Hash((key ⊕ ipad) || message))。

HMACの使用場面:APIのWebhook署名の検証(Stripe、GitHub、ShopifyはすべてHMAC-SHA256を使用)、改ざん防止トークンの作成(JWTの署名はHS256アルゴリズムでHMAC-SHA256を使用)、攻撃者による送信中のデータ改変がないことの検証。鍵は秘密に保たれなければなりません — 攻撃者が鍵を持っていればHMACは保護を提供しません。

よくある間違い:HMAC の代わりにHash(key + message)を使うこと。これはLength Extension攻撃に脆弱です — Hash(key + message)を知っている攻撃者は、鍵を知らなくてもHash(key + message + attacker_data)を計算できます。SHA-256とMD5の両方がこれに脆弱です。HMACの二重ハッシュ構造がこれを防ぎます。SHA-3はLength Extensionに脆弱ではありませんが(内部構造が異なる)、一貫性のためにHMACを使ってください。

コードでは:Node.jsにcrypto.createHmac("sha256", key).update(message).digest("hex")があります。Pythonにはhmac.new(key, message, hashlib.sha256).hexdigest()があります。HMACを自分で実装しないでください。比較でのタイミング攻撃(===の代わりにcrypto.timingSafeEqualを使う必要性)は、正しいHMACをバイト単位で漏洩させる可能性があります。

ハッシュ関数のよくある間違い

間違い1:パスワードにSHA-256を使う。SHA-256は高速です — パスワードには悪いことです。RTX 4090は毎秒220億のSHA-256ハッシュを計算します。全ASCII集合(6.6京通り)の8文字パスワードは3.5日で陥落します。bcrypt/Argon2idを使えば、それが数世紀になります。SHA-256(ソルト付きでも)でユーザーパスワードを保存しているなら、今すぐ移行してください。

間違い2:セキュリティコンテキストで==によるハッシュ比較。文字列比較は最初の異なる文字でショートサーキットし、タイミング情報を漏洩します。攻撃者はレスポンス時間を測定することで正しいHMACを1文字ずつ特定できます。定数時間比較を使ってください:Node.jsのcrypto.timingSafeEqual()、Pythonのhmac.compare_digest()、Goのsubtle.ConstantTimeCompare()。

間違い3:「短いID」のためにハッシュを切り詰める。SHA-256ハッシュの先頭8文字(32ビット)を取ると、衝突確率はわずか77,000アイテムで50%に跳ね上がります(誕生日のパラドックス)。URLショートナーやキャッシュキーでこれを見たことがあります。短い識別子が必要なら、暗号ハッシュを切り詰めるのではなく、専用の短いIDジェネレータ(nanoid、hashids)を使ってください。

間違い4:ハッシュ=暗号化と思い込む。ハッシュは一方向です — 出力から入力を復元できません。暗号化は双方向です — 鍵があればデコードできます。後で読む必要のあるデータ(APIキー、クレジットカード番号)を保存するなら暗号化(AES-256-GCM)を使います。保存せずにデータを検証するなら(パスワード、完全性チェック)ハッシュを使います。これらは根本的に異なる操作です。