ISO イメージをダウンロードしたときに「SHA-256 ハッシュを検証してください」と書かれていて面倒だなと思ったこと、パスワード保存の実装で「MD5 で十分では?」と同僚に聞かれて返答に詰まったこと、JWT の署名アルゴリズムが HS256 になっている意味を調べようとして沼にハマったこと——こういう場面で必ず出てくるのがハッシュ関数です。ざっくり「データを固定長の文字列に変換するやつ」と覚えている人は多いと思うけど、なぜ MD5 は「壊れた」と言われるのか、SHA-256 でパスワードを保存してはいけない理由は何か、チェックサムと暗号学的ハッシュの境界はどこか、ちゃんと説明できる人は意外と少ないよね。本記事ではハッシュ関数の基礎から、2017 年の SHAttered 攻撃、bcrypt / Argon2 によるパスワード保存、HMAC の役割まで、実務で迷いやすいポイントを整理していきます。
どちらも「データから短い値を作る」点は同じですが、目的と強度がまったく違います。CRC32 や Adler32 のようなチェックサムは、通信経路上で起きる偶発的なビット誤りを検出するのが仕事。高速で低コストですが、意図的な改ざんには無力で、入力を少しいじれば同じ値にぶつけるのもそこまで難しくありません。一方 SHA-256 のような暗号学的ハッシュ関数は、意図的に同じ値を作ろうとする攻撃者を前提に設計されています。一方向性(元データを逆算できない)、衝突耐性(同じ出力を持つ別入力を作れない)、雪崩効果(1 ビットの変化で出力の半分が反転)といった強い性質を満たすように設計され、改ざん検知や電子署名のような「敵を想定する」用途でも使えます。CRC は「通信線のノイズ対策」、SHA-256 は「悪意のある攻撃者への対策」、と目的が違うと覚えておきましょう。
ハッシュとは(一方向関数)
ハッシュ関数とは「任意の長さの入力を受け取り、固定長のビット列を返す関数」です。SHA-256 なら入力がどれだけ長くても(空文字列でも、数ギガバイトの動画でも)出力は必ず 256 ビット = 64 文字の 16 進数になります。この出力をハッシュ値・ダイジェスト・メッセージダイジェストなどと呼びます。呼び名は色々ありますが指しているものは同じです。
ハッシュ関数には 4 つの重要な特徴があります。1 つめは決定性で、同じ入力を渡せば同じハッシュが返ってきます。ランダム性はなく、世界中どこで計算しても一致するので、「このファイルの SHA-256 は abc123... です」と公開すれば受け取り側が同じ値を計算して改ざんの有無を確認できます。2 つめは高速性。現代の CPU や GPU なら 1 秒間に数十 GB をハッシュ化できるので、ファイルチェックやデータ処理の中に組み込んでも体感上ほぼ気になりません。
3 つめは雪崩効果(avalanche effect)です。入力を 1 ビットだけ変えただけで、出力のハッシュが約半分のビットで反転します。例えば hello と hellp(最後を 1 文字変えただけ)の SHA-256 を比べると、値はまったく別物に見えます。この性質により、わずかな改ざんでもハッシュが派手に変わるので、違いが一目で分かるようになります。4 つめが本章のタイトルにもある一方向性。ハッシュ値から元の入力を逆算するのは、現在の計算資源では事実上不可能なように設計されています。暗号学では「前向きに計算するのは簡単だが逆向きは難しい」関数を一方向関数と呼び、ハッシュ関数はその代表例です。
イメージとしては「ミキサーに食材を入れる」のに近いです。トマトとバナナをミキサーにかけるとオレンジ色のジュースができますが、そのジュースから元のトマトとバナナを再構築するのは不可能——ハッシュ関数もそれと同じで、計算は簡単だけど逆戻りはできません。
暗号化との違い
「暗号化もハッシュも、データを意味不明な文字列に変換するやつ」という雑な括りで混同している人をたまに見かけますが、両者は目的も仕組みも決定的に違います。ざっくり言えば、暗号化は双方向・可逆・鍵が必要、ハッシュは一方向・不可逆・鍵なしです。
暗号化は「後で元に戻す必要がある」データを保護するために使います。AES-GCM で暗号化したファイルは、同じ鍵を持つ人がいれば復号して元の内容を取り戻せます。目的は機密性——第三者に中身を読まれないようにすることです。対してハッシュ関数は「元に戻せないこと」そのものが価値で、目的は完全性の検証や同一性の確認です。パスワード保存で使うときも、サーバーが本当に欲しいのは「入力されたパスワードが保存時と同じか」だけで、元のパスワードそのものは知らなくていい——だから一方向のハッシュで十分、むしろ一方向である方が情報漏洩に強いわけです。
「じゃあパスワードを暗号化して DB に保存すれば、鍵を守ればいいだけでは?」と思うかもしれません。これが罠で、暗号鍵は必ずサーバーのどこかに存在する必要があるため、DB ダンプを取られた攻撃者がサーバーにも侵入していれば一緒に盗まれてしまいます。ハッシュなら鍵は存在せず、そもそも復元できないので「DB だけ漏れたときの安全性」が高いというわけです。両者の違いを表にまとめておきます。
| 観点 | 暗号化(AES など) | ハッシュ関数(SHA-256 など) |
|---|---|---|
| 方向性 | 双方向(暗号化と復号) | 一方向(復元不可) |
| 鍵の有無 | 必須 | 不要 |
| 出力長 | 入力に比例(ほぼ同じ長さ) | 固定長 |
| 主な目的 | 機密性の確保 | 完全性・同一性の検証 |
| 代表例 | AES-GCM、ChaCha20-Poly1305 | SHA-256、SHA-3、BLAKE3 |
覚え方は単純で「戻す必要があるなら暗号、戻す必要がないならハッシュ」。この切り分けさえできれば、セキュリティ設計で道具を取り違えることはかなり減ります。
良いハッシュ関数の性質
暗号学的ハッシュ関数に求められる性質は、大きく 4 つあります。名前だけ聞くと難しそうですが、攻撃者の立場で考えると意味は素直に入ってきます。
1. 原像計算困難性(Pre-image resistance)
出力されたハッシュ値 h が与えられたとき、そこから hash(x) = h となる入力 x を見つけるのが計算量的に困難、という性質です。要するに逆算できないということ。SHA-256 なら約 2²⁵⁶ 通りの入力空間を総当たりする必要があり、地球上のすべての計算機を動員しても現実的な時間では終わりません。パスワードのハッシュから元のパスワードを復元できないのは、この性質のおかげです。
2. 第二原像計算困難性(Second pre-image resistance)
ある入力 x1 が与えられているときに、hash(x1) = hash(x2) となる別の入力 x2 を見つけるのが困難、という性質です。「原像」との違いがわかりにくいですが、「先にどちらか片方の入力が決まっている」のがポイント。例えば攻撃者が契約書 A の改ざん版 B を作りたいとき、B のハッシュが A と一致していないと気づかれてしまう——これを阻止するのが第二原像耐性です。
3. 衝突計算困難性(Collision resistance)
任意の 2 つの入力 x1・x2 で hash(x1) = hash(x2) となるペアを作るのが困難、という性質です。第二原像との違いは「入力の両方を攻撃者が自由に選べる」点。攻撃者が有利になる分だけ必要な計算量が減り、誕生日パラドックスの考え方から、n ビットのハッシュなら約 2^(n/2) 回の試行で衝突が見つかります。SHA-256 ならおよそ 2¹²⁸ 回で、これは現実的には不可能な回数ですが、SHA-1(160 ビット)は 2⁸⁰ 回で、もう「現実的な範囲」に入ってしまっています。
4. 雪崩効果(Avalanche effect)
入力を 1 ビットだけ変えただけで、出力のビットが約半分反転するという性質です。数学的に強い要件ではないですが、前述の衝突計算困難性や原像計算困難性を満たすために実用上必要な性質で、よく「このハッシュ関数は良い雪崩効果を持つ」という表現で評価されます。雪崩効果が弱いハッシュは、入力の一部から出力の一部を推測できてしまうので、もう暗号学的には使い物になりません。
これら 4 つの性質をすべて満たしていて初めて「暗号学的ハッシュ関数」と呼べます。次章で見る MD5 と SHA-1 は、衝突計算困難性が崩れたことで「壊れた」とみなされているわけです。
世代史(MD5 / SHA-1 / SHA-2 / SHA-3)
ハッシュ関数の世代交代は、だいたい「新しい攻撃が発表されて、より強い次世代に置き換わる」の繰り返しです。主要な暗号学的ハッシュの年表を追ってみると、標準化と破綻がきれいに交互に起きていることがわかります。
| アルゴリズム | 登場 | 出力長 | 設計者 | 現在のステータス |
|---|---|---|---|---|
| MD5 | 1992 | 128 bit | Rivest(MIT) | 衝突破綻・用途を選ぶ |
| SHA-1 | 1995 | 160 bit | NSA / NIST | 衝突破綻・非推奨 |
| SHA-2(256/384/512) | 2001 | 256〜512 bit | NSA / NIST | 現役・推奨 |
| SHA-3(Keccak) | 2015 | 224〜512 bit | Bertoni らベルギー組 | 現役・SHA-2 と並行 |
| BLAKE2 | 2012 | 最大 512 bit | Aumasson ら | 高速・推奨 |
| BLAKE3 | 2020 | 任意長 | O'Connor ら | 並列処理で最速クラス |
MD5 は Ron Rivest(RSA の R)が 1992 年に設計し、長らく業界標準として君臨しました。続く SHA-1 は NSA(アメリカ国家安全保障局)が設計し、NIST(米国標準技術研究所)が 1995 年に標準化。Windows や Git、多くの HTTPS 証明書で使われ、事実上の世界標準になりました。SHA-2 ファミリー(SHA-256、SHA-384、SHA-512 など)は 2001 年に同じ NSA が設計した後継で、SHA-1 の設計思想を引き継ぎつつビット長を拡大して安全マージンを取っています。
SHA-3 は少し毛色が違います。NIST が「SHA-2 もいつか破られるかもしれないから、まったく別の設計の予備を用意しておこう」と考えて 2007 年に国際公募コンペを開催し、ベルギー勢の Keccak が 2012 年に選ばれ、2015 年に FIPS 202 として正式標準になりました。SHA-2 と SHA-3 は構造(Merkle-Damgård 構造と Sponge 構造)がまったく違うため、一方が破られてももう一方は生き残る、という保険になっています。並行して BLAKE2、BLAKE3 といった高速な現代的ハッシュも登場していて、特に BLAKE3 は SIMD / 並列処理で SHA-256 より何倍も速いことから、ファイル同期ツールやバックアップソフトで採用が広がっています。
MD5 / SHA-1 はなぜ「壊れた」か
「MD5 は使うな」「SHA-1 は非推奨」と聞いたことがあっても、具体的にいつ・どうやって壊れたのかまで知っている人は多くありません。ちゃんと時系列で追っておきましょう。
MD5 の破綻
MD5 の衝突耐性に最初の穴を開けたのは、2004 年の王小雲(Wang Xiaoyun)らによる論文です。当時 Crypto 2004 会議で電撃的に発表され、MD5 の衝突が数時間で見つけられることが示されました。攻撃者が 2 つの異なるデータで同じ MD5 値を持つペアを作れる、という意味で、これは本来「地球規模の計算資源でも無理」なはずでした。その後 2008 年には Stevens らによるchosen-prefix collision攻撃が成功し、さらに驚くべきことに、攻撃者は不正な認証局(Rogue CA)証明書を作り出すことに成功しました。具体的には、正規の CA に署名させた証明書と、攻撃者が用意した「中間 CA 権限を持つ不正証明書」の MD5 を一致させ、正規の署名を流用するという攻撃です。2012 年には、この手法を応用した Flame マルウェアが Windows Update を汚染しました。この事件を契機に、主要な CA は MD5 を完全に切り捨てました。
SHA-1 の破綻
SHA-1 の寿命も長くはありませんでした。2005 年には理論的に攻撃の可能性が示唆されていましたが、決定打となったのは 2017 年 2 月のSHAttered 攻撃です。Google と CWI(オランダ数学情報学センター)の共同研究チームが、異なる内容を持ちながら SHA-1 値が一致する 2 つの PDF ファイルを実際に作り出して公開しました。計算には約 6500 年分の CPU 時間と 110 年分の GPU 時間が必要でしたが、当時の Google のインフラでは数十日で終わる規模です。2020 年には Leurent と Peyrin による chosen-prefix collision も成功し、攻撃コストはさらに下がっていきました。
この流れを受けて、Git は 2020 年から SHA-256 への移行を段階的に進めており、TLS 証明書は 2017 年を境にブラウザが SHA-1 署名を拒否するようになりました。MD5 と SHA-1 は「衝突耐性が破れた」とされており、改ざん検知や電子署名のような「攻撃者を想定する用途」では使ってはいけません。ただし「偶発的な通信エラー検知」のような非暗号用途(データの重複検出やキャッシュキー)では、速度を優先してまだ使われる場面もあります。
用途別の選び方
ハッシュ関数といっても、用途によって適切なアルゴリズムは変わります。ここでは代表的な用途ごとに「何を使えばいいか」を整理します。
完全性検証・電子署名
ファイルの改ざん検知、電子署名、TLS 証明書、ブロックチェーンなど「攻撃者を想定する用途」では、SHA-256(または SHA-384 / SHA-512)を選べば間違いありません。SHA-3 も選べますが、エコシステムの広さでは SHA-2 ファミリーに分があり、特別な理由がなければ SHA-256 でよいです。将来的に量子計算機を警戒するなら SHA-384 以上を選ぶ、という選択肢もあります。
パスワード保存
これは後述の専用章で詳しく扱いますが、結論だけ先に言うと Argon2id(または bcrypt・scrypt)を使います。素の SHA-256 で保存してはいけません。
データ重複排除・キャッシュキー
ファイル同期ツールで「内容が同じファイルを検出して 1 つにまとめる」、CDN でキャッシュキーを作る、といった用途では速度が重要です。ここでは暗号学的強度より速度を優先し、BLAKE3、xxHash、古いコードなら MD5 も現役で使われています。攻撃者が衝突を作って乗っ取る動機がなければ(例:ローカルの同期ツールなど)、これで十分です。
分散ハッシュテーブル・ロードバランシング
コンシステントハッシュのような「均等にばらけさせる」用途では、MurmurHash や FNV のような非暗号学的ハッシュが使われます。これらは暗号学的性質は満たしませんが、統計的にバランスがよく、SHA-256 より 10 倍以上速いのがメリットです。
PoW(Proof of Work)
ビットコインのマイニングは「SHA-256 を 2 回適用した結果が、ある閾値以下になる nonce を探す」という作業です。ここで SHA-256 が選ばれているのは、衝突計算が難しく、GPU / ASIC でも並列化しやすいから。逆に言えば、ハッシュの計算が速すぎないこと(Argon2 ベースの PoW もある)が望まれる場面もあります。
ファイル整合性の検証
Linux ディストリビューションの ISO イメージや、アプリケーションのインストーラをダウンロードしたとき、公式サイトに SHA256SUMS というファイルが一緒に置かれているのを見たことがあるはず。これは「このファイルの正しい SHA-256 値はこれです」というリストで、受け取った側が手元で計算して一致すれば、転送中に壊れていないこと・改ざんされていないことの両方を確認できます。
Ubuntu や Debian、Fedora、Arch Linux などの主要ディストリは必ずこのハッシュリストを公開しており、検証コマンドも標準で備わっています。macOS や Linux なら sha256sum、Windows PowerShell なら Get-FileHash が同じことをします。
# 単一ファイルのハッシュを計算
sha256sum file.iso
# → e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 file.iso
# macOS ネイティブの shasum コマンド
shasum -a 256 file.iso
# Windows PowerShell
Get-FileHash -Algorithm SHA256 file.iso複数ファイルをまとめて検証するときは、公式の SHA256SUMS ファイルを落として -c オプションで照合するのが定番です。
# SHA256SUMS の内容例
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ubuntu-24.04.iso
5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 ubuntu-24.04-netboot.iso
# ファイルごとに照合
shasum -a 256 -c SHA256SUMS
# → ubuntu-24.04.iso: OK
# ubuntu-24.04-netboot.iso: OKただしここで落とし穴があります。ハッシュ値そのものが改ざんされていたらどうなる?という問題です。攻撃者が ISO と SHA256SUMS の両方を差し替えれば、整合性チェックは通ってしまいます。そこで多くのディストリは SHA256SUMS ファイルにGPG 署名を付けて配布しています。SHA256SUMS.gpg という署名ファイルを同時にダウンロードし、公式の公開鍵で検証することで「このハッシュリストは確かに公式が作ったもの」と保証できる、という二段構えです。
# GPG 署名の検証
gpg --verify SHA256SUMS.gpg SHA256SUMS
# → Good signature from "Ubuntu CD Image Automatic Signing Key"
# その後に shasum で中身を検証
shasum -a 256 -c SHA256SUMS「公式が公開鍵の指紋(フィンガープリント)を複数の経路で広めておく」ことで、最初の信頼点を確立します。こうした信頼の連鎖は OSS の世界ではごく普通の仕組みで、配布物のセキュリティ設計の基本になっています。
パスワード保存の正しい方法
ここは多くの開発者が間違える章です。「パスワードはハッシュ化して保存しましょう」まではほぼ誰でも知っていますが、どのハッシュを使うかで安全性はまるで変わります。結論から言うと、素の SHA-256 でパスワードを保存してはいけません。
SHA-256 は確かに衝突耐性も一方向性も十分ですが、高速すぎるのが問題です。現代の GPU は 1 秒間に数十億回 SHA-256 を計算できるので、よくあるパスワードの辞書をすべて試しても 1 時間かかりません。そのうえ同じパスワードは同じハッシュになるため、漏洩した DB から「5e88...(= password の SHA-256)」のようなパターンを見つけるだけで大量のアカウントを一瞬で破れてしまいます。パスワード保存には、わざと計算を遅くしてある鍵導出関数(KDF)を使います。
レインボーテーブルとソルト
攻撃者は事前に「よくあるパスワード × そのハッシュ値」の巨大な対応表を作って持っています。これをレインボーテーブルと呼び、DB ダンプを手に入れた攻撃者はハッシュ値を表で引くだけで元のパスワードを逆引きできてしまいます。対策はソルト(salt)——ユーザーごとにランダムな文字列を生成し、パスワードと連結してからハッシュ化する方法です。ソルトがあれば同じパスワードでも別のハッシュになり、レインボーテーブルは事実上使えなくなります。
ストレッチングと KDF
ソルトだけでは不十分で、もう 1 つストレッチング——わざと計算を重くして攻撃者の 1 回あたりのコストを上げる仕組みが必要です。これを自動でやってくれるのが bcrypt、scrypt、Argon2 といった専用の関数(KDF = Key Derivation Function)で、2026 年現在のベストプラクティスはArgon2id、次点で bcrypt です。下の表に、よく見る保存方式の比較をまとめます。
| 保存方式 | 安全性 | 評価 |
|---|---|---|
| 平文 | 論外 | 絶対 NG。漏洩したら全ユーザー即死 |
| MD5 | 論外 | 衝突破綻・高速すぎ・レインボーテーブル祭り |
| SHA-256(ソルトなし) | NG | レインボーテーブルに弱い |
| ソルト + SHA-256 | 不十分 | GPU で力ずくに破られる |
| bcrypt | 可 | 長く使われている定番・依然安全 |
| scrypt | 良 | メモリ負荷も加わる |
| Argon2id | 最善 | 2015 年 PHC 優勝・2026 年の推奨 |
bcrypt のハッシュは見た目で区別できます。$2b$12$... のように $ で区切られた形式で、2b がバージョン、12 がコスト係数(計算回数の 2 のべき乗指数)、続いてソルトとハッシュが入っています。コスト係数を上げれば計算時間が倍になり、CPU の進化に合わせて値を調整できる仕組みです。
# bcrypt のハッシュ例(Node.js)
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash('mypassword', 12);
// → '$2b$12$KIXnL5j5.zvUj3MnZ9ZQHOeQZe8z3u0K...'
await bcrypt.compare('mypassword', hash); // → true新規プロジェクトでパスワード保存を実装するなら、迷わず Argon2id を選びましょう。OWASP や IETF RFC 9106 でも推奨されており、bcrypt や scrypt より新しい攻撃(GPU / ASIC による並列化)にも強い設計です。既存のシステムで bcrypt を使っているならそのままでも問題ありません——強度は今も十分で、わざわざ移行する緊急性はないです。新規システムだけ Argon2id に揃える、というのが現実的なラインです。
HMAC と鍵付きハッシュ
ハッシュ関数単体では「このメッセージが改ざんされていない」ことは示せても、「このメッセージが特定の送信者から来た」ことまでは保証できません。誰でもハッシュを計算できるので、攻撃者が途中でメッセージを差し替えてハッシュも作り直せば、受信側は気づけないからです。これを解決するのがHMAC(Hash-based Message Authentication Code)——秘密鍵を組み合わせた「鍵付きハッシュ」です。
HMAC の発想はシンプルで、「メッセージと秘密鍵を一緒にハッシュ化する」だけです。送信者と受信者が同じ鍵を共有していれば、受信者は手元で同じ HMAC を計算して比較できます。攻撃者は鍵を知らないので、正しい HMAC を作ることができません。実装は次のような感じです。
// Node.js の HMAC-SHA256
const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', 'my-secret-key');
hmac.update('hello world');
console.log(hmac.digest('hex'));
// → 'b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7'「単純に鍵とメッセージを連結してハッシュにすればいいのでは?」と思うかもしれません。hash(key || message) のように書くやつです。実はこれには長さ伸長攻撃(length extension attack)という弱点があり、Merkle-Damgård 構造のハッシュ(MD5・SHA-1・SHA-2)では「元のハッシュ値と長さを知っていれば、追加のメッセージをつなげた別 HMAC を鍵を知らずに作れる」という穴があります。HMAC はこれを避けるために、内側と外側で 2 回ハッシュを計算する二段構造になっています。数式で書くと HMAC(K, m) = H((K ⊕ opad) || H((K ⊕ ipad) || m))。ちゃんと実装されたライブラリを使えばこの内部構造を意識する必要はありませんが、「自前で書かずに標準ライブラリを呼ぶ」は鉄則です。
HMAC の実用例はそこら中にあります。AWS API のSignature V4は HMAC-SHA256 を使ってリクエストの署名を作っていますし、JWT の HS256 アルゴリズムも中身は HMAC-SHA256。Webhook の署名検証(GitHub、Stripe、Slack などが X-Hub-Signature ヘッダで送ってくるやつ)もほとんど HMAC-SHA256 です。共通鍵方式なので鍵管理の面倒さはありますが、RSA / ECDSA のような公開鍵暗号より桁違いに速く、大量リクエストをさばく API バックエンドで広く採用されています。
衝突攻撃の実例(SHAttered)
「理論上は衝突できるって言うけど、本当に 2 つの意味のあるデータで同じハッシュを作れたの?」——この疑問に実物で答えたのが、2017 年 2 月に Google と CWI が公開した SHAttered 攻撃です。このプロジェクトは 2 つの PDF ファイル shattered-1.pdf と shattered-2.pdf を公開し、両者は内容が明らかに違う(片方は青、片方は赤の背景)のに SHA-1 ハッシュが完全に一致する、という衝撃的なデモを示しました。
技術的な裏付けは chosen-prefix collision——攻撃者が任意のプレフィックスを 2 つ用意し、それぞれにうまく設計した「衝突ブロック」を追加することで同じハッシュを作る手法です。単純な衝突(どんな 2 つでもいいから同じハッシュのペア)よりずっと難しい攻撃ですが、成功すれば「契約書 A と契約書 B のハッシュが一致する」という意味のある攻撃が可能になります。SHAttered は総計 9.22 × 10¹⁸ 回の SHA-1 計算を必要としましたが、Google のインフラでは現実的な時間で終わる規模でした。
この発表以降の波紋はかなり大きく、Git がもっとも影響を受けたプロジェクトのひとつです。Git は内部で SHA-1 をコンテンツのアドレスとして使っていて、理論上は SHAttered と同じ手法で「別内容のファイルを同じハッシュで忍ばせる」攻撃が可能でした。Linus Torvalds は当初「Git のユースケースでは深刻ではない」とコメントしましたが、長期的には移行が必要と判断され、2020 年から SHA-256 ベースのリポジトリがサポートされるようになりました(git init --object-format=sha256)。既存リポジトリとの互換性のため移行は緩やかに進行中ですが、新規プロジェクトで SHA-256 を選ぶ選択肢は揃っています。
SHAttered の教訓は、「理論上の衝突耐性の破綻は、必ず現実の攻撃になる」ということです。MD5 のときも同じで、2004 年の王小雲の論文から 2008 年の Rogue CA 事件まで 4 年しかありませんでした。SHA-1 は 2005 年の理論的攻撃から 2017 年の実証まで 12 年かかりましたが、暗号学的アルゴリズムは「脆弱性が発表されたら使用を止める」ルールを守らないと、後から大きな代償を払うことになります。
ベンリーのハッシュ生成ツール使い方
ここまでの内容を手元で確かめたいときは、ベンリーの ハッシュ生成ツール が使えます。テキストでもファイルでも入力可能で、処理はすべてブラウザ内で完結するため、入力データがサーバーに送信されることはありません。機密情報を貼り付けても安全です。
対応しているアルゴリズムは次のとおりです。
- MD5: 128 ビット。レガシーシステムとの互換性確認用。セキュリティ用途には使わないでください。
- SHA-1: 160 ビット。Git のレガシーリポジトリなどでまだ見かけるので、確認用として残しています。新規用途では非推奨。
- SHA-256: 256 ビット。2026 年の現役主力。ファイル検証・署名・ブロックチェーンなど広範に使われています。
- SHA-384 / SHA-512: SHA-2 ファミリーの長尺版。より長いダイジェストが必要な用途や、将来の安全マージンを確保したいとき。
使い方はシンプルで、テキストエリアに文字列を貼り付けるとその場で各アルゴリズムのハッシュ値が計算されます。ファイルをドラッグ & ドロップすれば、100 MB クラスのファイルでもブラウザ内で処理できます。本記事で紹介した「1 ビット変えたら出力が大きく変わる(雪崩効果)」の様子も、hello と hellp を並べれば一瞬で確認できます。
コマンドラインや自前プログラムで SHA-256 を計算したいときの最短コードをまとめておきます。どの環境でもだいたい 1〜2 行で書けるのがハッシュ関数の便利なところです。
# シェル(coreutils)
echo -n hello | sha256sum
# → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 -
# macOS
echo -n hello | shasum -a 256
# OpenSSL(環境を問わず最も確実)
echo -n hello | openssl dgst -sha256
# → SHA2-256(stdin)= 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
# Node.js
const crypto = require('crypto');
crypto.createHash('sha256').update('hello').digest('hex');
// → '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
# Python
import hashlib
hashlib.sha256(b'hello').hexdigest()
# → '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
# Go
import "crypto/sha256"
sum := sha256.Sum256([]byte("hello"))
fmt.Printf("%x\n", sum)
# Ruby
require 'digest'
Digest::SHA256.hexdigest('hello')シェルの echo は末尾改行を勝手に付けてしまうので、正確に "hello"(5 バイト)をハッシュ化したいときは -n を忘れずに。付け忘れるとバイト数が変わり、まったく別のハッシュ値になります。本記事の数値と違う値が出てくるときは、まずこれを疑いましょう。
よくある質問
MD5 は完全に使っちゃいけない?
セキュリティが関わる用途では絶対に使わないでください。衝突耐性が破綻していて、電子署名・改ざん検知・パスワード保存には不適です。一方で「攻撃者を想定しない純粋な用途」——ファイル重複検出、キャッシュキー、ローカル DB のインデックスなど——では速度と長年の実装の安定性を理由に今も使われることがあります。MD5 の計算結果を「データの ID 的なもの」として使う分には悪くないですが、「改ざんを検出する」用途には二度と戻ってこない、というのが業界のコンセンサスです。迷ったら SHA-256 か BLAKE3 を選んでおけば間違いありません。
ソルトをなぜ足す必要がある?
ソルトを足さないと、攻撃者が事前計算した「よくあるパスワード × そのハッシュ値」の巨大な対応表(レインボーテーブル)でハッシュを一気に逆引きできてしまうからです。ソルトがユーザーごとに違えば、同じパスワードでも別のハッシュ値になるため、攻撃者はユーザー 1 人ずつ個別に総当たりするしかなくなります。ソルトは秘密ではないので DB にそのまま保存して構いません(bcrypt や Argon2 は自動的にハッシュ文字列の中にソルトを含めます)。大事なのは「ユーザーごとに十分ランダムで、長い(16 バイト以上が目安)」こと。短すぎたり固定値だったりすると意味がありません。
SHA-256 でパスワード保存はダメなの?
ダメです。SHA-256 自体は数学的には強力ですが、高速すぎるのが問題になります。現代の GPU は 1 秒間に数十億回の SHA-256 を計算でき、よくあるパスワード辞書程度なら 1 時間で全部試せてしまいます。パスワード保存では「正規の認証を数百 ms 待たせる代わりに、攻撃者には数年かかる」くらいの計算負荷を意図的に作る必要があり、そのために bcrypt・scrypt・Argon2 のような「わざと遅い」専用関数が用意されています。これらは内部で SHA-256 やその仲間を何万回も繰り返し適用するなどして、計算コストを上げる仕組みになっています。新規実装なら Argon2id が第一選択です。
SHA-3 と SHA-2 は何が違う?
内部構造がまったく違います。SHA-2(SHA-256 など)は Merkle-Damgård 構造という古典的な設計で、1 ブロックずつ処理する関数を繰り返し適用します。SHA-3 はSponge 構造という新しい設計で、状態に吸収(absorb)してから絞り出す(squeeze)という独自のやり方をします。この違いのおかげで、Merkle-Damgård 構造にある「長さ伸長攻撃」という既知の穴が SHA-3 では最初から存在しません。機能的には SHA-256 と SHA3-256 はどちらも 256 ビット出力で安全性も同等とされていますが、「SHA-2 が将来破られてもその攻撃は SHA-3 には効かない」という保険になります。実務上は SHA-256 で十分で、特別な理由がない限り SHA-3 に移行する必要はありません。
ハッシュを多段にすると強くなる?
「sha256(sha256(sha256(x))) のように 3 回重ねると強度が 3 倍に?」と思うかもしれませんが、ほとんど意味がありません。単に繰り返すだけでは衝突耐性も原像耐性も目に見えて強くはならず、むしろ実装を間違えて衝突を減らしてしまうリスクすらあります。「計算コストを上げたい」のが目的なら、それはストレッチングが解決する領域で、専用の KDF(bcrypt・scrypt・Argon2)が何十万回・何百万回とハッシュを繰り返してくれます。これらは「多段ハッシュ」と似ていますが、ソルト・メモリコスト・並列化耐性など追加の考慮をしているので、単純なループとは別物です。「自前で多段ハッシュを書く」ことは暗号学の鉄則で禁じ手の一つ。標準ライブラリと推奨 KDF を使いましょう。
ハッシュをサクッと試したいなら
ベンリーのハッシュ生成ツールは、テキストもファイルも貼り付けるだけで MD5・SHA-1・SHA-256・SHA-384・SHA-512 をその場で計算できます。すべてブラウザ内で処理されるので、機密データを扱っても安全です。
ハッシュ生成ツールを開く →