暗号ハッシュ関数:完全ガイド
暗号ハッシュ関数は、現代セキュリティを支える縁の下の力持ちです。ウェブサイトへのログイン、Gitへのコードのプッシュ、ファイルのダウンロード、ビットコイン取引——これらすべての場面で、ハッシュ関数は舞台裏で活躍しています。しかし多くの開発者は、毎日のように使いながらも、その本質を理解しておらず、誤用した場合に何が起きるかも知りません。
このガイドでは、数学的原理、歴史、破られたアルゴリズム、現代の標準、そしてハッシュ関数を正しく使うために必要な実践的なコードをすべて網羅します。
1. 暗号ハッシュ関数とは?
暗号ハッシュ関数は任意のサイズの入力を受け取り、固定サイズの出力(ダイジェストまたはハッシュ値)を生成します。例えば、SHA-256 は入力が1文字であれ映画ファイル全体であれ、常にちょうど256ビット(64個の16進数文字)を出力します。
基本特性
決定論的:同じ入力は常に同じ出力を生成します。SHA-256("hello") は常に 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 を返します。
高速な計算:ハッシュの計算はミリ秒単位で完了する必要があります。この効率性はファイル整合性チェックやデジタル署名において重要ですが、パスワードハッシュでは弱点になります(後述)。
一方向性(原像困難性):ハッシュ出力 H が与えられた場合、hash(m) = H となる入力 m を計算で見つけることは不可能です。ハッシュ値だけから元のデータを逆算できません。
第二原像困難性:入力 m1 が与えられた場合、hash(m1) = hash(m2) となる別の入力 m2 を計算で見つけることは不可能です。攻撃者が元のデータを知っていても、同じハッシュ値を生成する別の入力を見つけられません。
衝突困難性:hash(m1) = hash(m2) となる任意の異なる2つの入力 m1 と m2 を計算で見つけることは不可能です。これは第二原像困難性より強い要件です。
雪崩効果:入力のわずかな変化——1ビットのフリップでさえ——出力を完全に変えます。"hello" を "hellp" に変更すると、元のハッシュとは全く異なるハッシュ値が生成されます。
SHA-256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
SHA-256("hellp") = 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca7
この2つのハッシュ値には予測可能な関係がありません——これが雪崩効果です。
2. ハッシュアルゴリズムの歴史
MD5(1991年)
Ronald Rivest がMD4の改良版としてMD5を設計しました。128ビットのダイジェストを生成し、1990年代を通じてチェックサムやパスワード保存に広く採用されました。10年以上にわたり、MD5は多くのセキュリティアプリケーションのデフォルト選択でした。
SHA-1(1995年)
米国国家安全保障局(NSA)がデジタル署名標準の一部としてSHA-1(Secure Hash Algorithm 1)を設計しました。160ビットのダイジェストを生成します。SHA-1はTLS/SSL証明書、コード署名、Gitのオブジェクトストレージのメインのハッシュアルゴリズムとなりました。
SHA-2ファミリー(2001年)
同じくNSAが設計したSHA-2は、実際には6つの関数のファミリーです:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。最も広く使われているのはSHA-256とSHA-512で、それぞれ256ビットと512ビットのダイジェストを生成し、今日でも安全です。
SHA-3 / Keccak(2015年)
SHA-1の弱点が明らかになったことを受け、NISTはNSAのSHA-2設計とは全く異なる新しいハッシュ標準を求める公開コンペ(2007〜2015年)を開催しました。勝者は Guido Bertoni、Joan Daemen、Michaël Peeters、Gilles Van Assche が設計した Keccak でした。Merkle–Damgård構造を使用するSHA-2とは異なり、SHA-3はスポンジ構造を使用し、根本的に異なるセキュリティプロファイルを提供します。
BLAKE2(2012年)
BLAKE2はMD5より高速でありながら、SHA-3レベルのセキュリティを提供する暗号ハッシュ関数です。SHA-3コンペの決勝進出者だったBLAKEを改良したものとして、Jean-Philippe Aumasson、Samuel Neves、Zooko Wilcox-O'Hearn、Christian Winnerleinが設計しました。BLAKE2bは64ビットプラットフォーム向けに最適化され、BLAKE2sは32ビット向けに最適化されています。
3. SHA-256の仕組み
SHA-256はMerkle–Damgård構造を使用します:メッセージは固定サイズのブロックに分割され、圧縮関数が反復的に適用され、1つのブロックの出力が次のブロックの入力になります。
ステップ1:パディング
入力メッセージは合計長が512ビットの倍数になるようにパディングされます。1 ビットが1つ追加され、続いてゼロが複数追加され、最後に元のメッセージ長が64ビットのビッグエンディアン整数として追加されます。
ステップ2:メッセージスケジュール
各512ビットブロックは、ビットを混合・回転するスケジュールを使って64個の32ビットワードに展開されます。W[0] から W[15] はメッセージブロックから直接来ます。W[16] から W[63] は次のように計算されます:
W[i] = σ1(W[i-2]) + W[i-7] + σ0(W[i-15]) + W[i-16]
σ0とσ1は特定のビット回転とシフト演算です。
ステップ3:圧縮——64ラウンド
SHA-256は8つの作業変数(aからh)を維持し、最初の8つの素数の平方根の小数部分で初期化されます。64ラウンドの各ラウンドで:
T1 = h + Σ1(e) + Ch(e,f,g) + K[i] + W[i]
T2 = Σ0(a) + Maj(a,b,c)
h = g; g = f; f = e; e = d + T1
d = c; c = b; b = a; a = T1 + T2
ラウンド定数 K[i] は最初の64個の素数の立方根の小数部分です。この設計は定数を公開検証可能にすることで、隠れたバックドアへの懸念を払拭しています。
ステップ4:出力
すべてのブロックを処理した後、8つの作業変数が初期ハッシュ値に加算され、最終的な256ビットダイジェストが生成されます。この「フィードフォワード」により、各ブロックの出力がすべての前のブロックに依存することが保証されます。
4. MD5とSHA-1が破られた理由
MD5衝突(2004年)
2004年、王小雲らがMD5に対する実際の衝突攻撃——同じMD5ハッシュを生成する2つの異なる入力を見つけること——を実証しました。2008年までに、研究者はMD5衝突を利用して実際のCAから偽のSSL証明書を偽造し、HTTPSインフラへの現実世界の攻撃を実証しました。
この攻撃は高度な差分暗号解読を使用し、現代のハードウェアで数秒以内にMD5衝突を生成できます。
SHA-1「SHAttered」攻撃(2017年)
GoogleのProject ZeroチームとCWI Amsterdamは2017年に最初の実際のSHA-1衝突を生成しました。SHAtteredと呼ばれるこの攻撃では、同一のSHA-1ハッシュを持つ2つの異なるPDFファイルが生成されました。この攻撃には約9.2 × 10¹⁸回のSHA-1計算が必要でした——シングルCPUで6,500年相当ですが、GPUなら約110年で可能であり、国家レベルや大規模組織には十分実行可能です。
実際への影響
MD5とSHA-1は以下の用途には安全ではありません:
- デジタル署名
- 証明書フィンガープリント
- パスワード保存
- セキュリティに敏感なすべてのアプリケーション
以下の用途にはまだ許容されます:
- 非暗号学的チェックサム(信頼できるチャンネルでのファイルダウンロードの整合性確認)
- ハッシュテーブルの検索
- 非セキュリティ向けの重複排除
- レガシーシステムの互換性(適切な注意書きを付けて)
5. 実際のユースケース
パスワード保存
パスワードを平文で保存することは絶対にしてはいけません——単純なハッシュ形式でも同様です。データベースが漏洩した場合、攻撃者は辞書攻撃やレインボーテーブルを使って数時間から数日以内に平文ハッシュを解読できます。
正しいアプローチは、パスワード専用に設計された遅い、ソルト付きハッシュ関数を使用することです:bcrypt、scrypt、またはArgon2。
ファイル整合性確認
ソフトウェアをダウンロードすると、開発者はSHA-256チェックサムを提供します。ダウンロード後、ファイルのハッシュを計算して比較します。一致する場合、ファイルは転送中に破損または改ざんされていません。
sha256sum downloaded-file.tar.gz
# 開発者が公開しているチェックサムと比較
デジタル署名
ハッシュ関数はデジタル署名の基礎です。ドキュメント全体(数GBになる可能性)に署名するのではなく、ハッシュ値にのみ署名します。受信者はドキュメントを独立してハッシュし、そのハッシュに対して署名を検証します。
ブロックチェーン
ビットコインは、プルーフ・オブ・ワークのマイニングとトランザクションブロックのハッシュにSHA-256を2回適用(SHA-256d)します。マイナーは、ハッシュすると特定の数のゼロが先頭に並ぶ出力が得られる入力(ナンス)を見つけなければなりません——このプロセスには膨大な計算量が必要で、ブロックチェーンのセキュリティ保証を提供します。
Gitオブジェクトストレージ
Gitはすべてのコミット、ツリー、blobオブジェクトのハッシュにSHA-1を使用します。ハッシュはオブジェクトの識別子と整合性チェックの両方の役割を果たします。SHA-1の弱点により、GitはSHA-256への移行を積極的に進めています。
ストレージの重複排除
バックアップシステムやコンテンツアドレス指定ストレージ(IPFSなど)は、重複コンテンツを識別するためにハッシュを使用します。2つのファイルのハッシュが同じであれば、1回だけ保存されます。
6. 実際のハッシュ計算
JavaScript (Node.js)
const crypto = require('crypto');
// SHA-256
const sha256 = crypto.createHash('sha256')
.update('Hello, World!')
.digest('hex');
console.log(sha256);
// 315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3
// MD5(非セキュリティ用途のみ)
const md5 = crypto.createHash('md5')
.update('Hello, World!')
.digest('hex');
console.log(md5);
// 65a8e27d8879283831b664bd8b7f0ad4
// SHA-512
const sha512 = crypto.createHash('sha512')
.update('Hello, World!')
.digest('hex');
console.log(sha512);
Python
import hashlib
# SHA-256
h = hashlib.sha256(b"Hello, World!").hexdigest()
print(h)
# 315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3
# SHA-512
h512 = hashlib.sha512(b"Hello, World!").hexdigest()
print(h512)
# 複数のアルゴリズム
for algo in ['md5', 'sha1', 'sha256', 'sha512']:
h = hashlib.new(algo, b"Hello, World!").hexdigest()
print(f"{algo}: {h}")
Bash / シェル
# SHA-256
echo -n "Hello, World!" | sha256sum
# 315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3 -
# MD5
echo -n "Hello, World!" | md5sum
# 65a8e27d8879283831b664bd8b7f0ad4 -
# SHA-1
echo -n "Hello, World!" | sha1sum
# ファイルをハッシュ
sha256sum /path/to/file.iso
7. HMAC:ハッシュベースのメッセージ認証コード
単純なハッシュはデータの整合性を検証します——データが破損しているかどうかを教えてくれます。しかし真正性は検証しません——誰がデータを作成したかは証明できません。誰でもハッシュを計算できるからです。
HMAC(RFC 2104)は秘密鍵をハッシュ関数と組み合わせることでこの問題を解決します:
HMAC(K, m) = hash((K' ⊕ opad) || hash((K' ⊕ ipad) || m))
ここで K' はブロックサイズにパディングされた鍵、opad/ipad は特定のパディング定数です。この構造は、基礎となるハッシュ関数が安全であれば、証明可能に安全です。
一般的な用途
API認証:REST APIはHMAC-SHA256を使ってリクエストに署名します。サーバーとクライアントは秘密鍵を共有します。クライアントは鍵でリクエストボディに署名し、サーバーはその署名を検証します。
JWTの署名:JSON Web TokenはHMAC-SHA256(HS256)を使ってヘッダーとペイロードに署名し、トークンが改ざんされていないことを保証します。
Webhookの検証:GitHub、Stripe、その他多くのサービスがHMAC-SHA256でWebhookペイロードに署名しており、受信者はペイロードが本物かどうかを検証できます。
HMACの計算
// Node.js
const crypto = require('crypto');
const hmac = crypto.createHmac('sha256', 'my-secret-key')
.update('message to authenticate')
.digest('hex');
console.log(hmac);
import hmac
import hashlib
key = b'my-secret-key'
message = b'message to authenticate'
sig = hmac.new(key, message, hashlib.sha256).hexdigest()
print(sig)
8. レインボーテーブルとソルト
レインボーテーブルとは?
レインボーテーブルは、既知のハッシュ値を元の平文入力にマッピングする事前計算されたデータベースです。攻撃者がパスワードハッシュのデータベースを入手した場合、各ハッシュを個別にクラックする必要はありません——テーブルで検索するだけです。
MD5とSHA-1については、長さ8文字以下のすべてのASCIIパスワードをカバーするレインボーテーブルが何年もの間無料で入手可能です。CrackStationなどのウェブサイトは、数十億のハッシュとパスワードのマッピングデータベースを維持しています。
ソルトがレインボーテーブルを無効化する方法
ソルトはハッシュ化の前にパスワードに追加されるランダムな値です:
hash(salt + password) = stored_hash
ソルトはハッシュと一緒に保存されます(秘密にする必要はありません)。各ユーザーに固有のランダムなソルトが付与されるため、攻撃者は事前計算されたテーブルを使用できません——考えられるすべてのソルト値に対して別々のレインボーテーブルが必要となり、計算上不可能です。
bcrypt:自動ソルトと意図的な低速化
bcryptは1999年にパスワードハッシュ専用に設計されました。ランダムなソルトを自動的に生成・組み込み、ハッシュ計算の速度を制御するコストファクターを含みます:
const bcrypt = require('bcrypt');
// パスワードをハッシュ(コストファクター12——現代のハードウェアで約250ms)
const hash = await bcrypt.hash('ユーザーパスワード', 12);
// 検証
const isMatch = await bcrypt.compare('ユーザーパスワード', hash);
保存されるハッシュの形式:$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW
$2b$12$ プレフィックスにアルゴリズムのバージョンとコストファクターがエンコードされています——bcryptはすべてを自動的に処理します。
9. アルゴリズム比較表
| アルゴリズム | 出力サイズ | 速度 | セキュリティ状態 | 最適な用途 |
|---|---|---|---|---|
| MD5 | 128ビット | 非常に速い | ❌ 破られた(衝突) | 非セキュリティチェックサムのみ |
| SHA-1 | 160ビット | 速い | ❌ 破られた(SHAttered) | レガシーシステムのみ |
| SHA-256 | 256ビット | 速い | ✅ 安全 | 汎用、TLS、署名 |
| SHA-512 | 512ビット | 64ビットで速い | ✅ 安全 | 高セキュリティアプリ |
| SHA-3/Keccak | 可変 | 中程度 | ✅ 安全 | SHA-2の代替 |
| BLAKE2b | 可変 | 非常に速い | ✅ 安全 | 性能重視のハッシュ |
| bcrypt | 184ビット | 遅い(意図的) | ✅ 安全 | パスワード保存 |
| Argon2id | 可変 | 遅い(意図的) | ✅ 安全 | パスワード保存(推奨) |
10. パスワードハッシュのベストプラクティス
パスワードはユーザーアカウントの鍵であるため、特別な扱いが必要です。パスワードデータベースの漏洩は壊滅的な結果をもたらす可能性があります。例外なく以下のルールに従ってください:
ルール1:平文パスワードを保存しない
これは明らかなことのように思えますが、今でも起きています。2019年、Facebookが社内で数億個のパスワードを平文で保存していたことが発覚しました。
ルール2:パスワードに高速ハッシュを使用しない
MD5、SHA-1、SHA-256、SHA-512はすべてパスワードハッシュには速すぎます。現代のGPUは1秒間に数十億のSHA-256ハッシュを計算でき、数時間でブルートフォース攻撃が可能です。
ルール3:パスワード専用のハッシュアルゴリズムを使用する
bcrypt(推奨最低ライン):コストファクター12以上を使用。広くサポートされ、実績があります。
scrypt:メモリハード型で、GPUとASIC攻撃への耐性があります。メモリとCPUコストを設定可能。
Argon2id(現在の推奨):2015年パスワードハッシュコンペの優勝者。Argon2idはサイドチャネル攻撃と時間-メモリトレードオフ攻撃の両方に耐性があるため、推奨されるバリアントです。最低設定:
- メモリ:64 MB
- 反復回数:3
- 並列度:4
ルール4:パスワードごとに固有のソルトを使用する
bcrypt/scrypt/Argon2(自動ソルトを含む)を使用している場合でも、その重要性を理解してください:同一のパスワードは異なるハッシュを生成しなければならず、1つが侵害されても他のものが明かされないようにする必要があります。
ルール5:時間の経過とともにコストファクターを調整する
ハードウェアが速くなるにつれてコストファクターを上げてください。bcryptなら約250〜500msを目標に。次回ログイン時にパスワードを再ハッシュします。
ルール6:ペッパーの使用を検討する
ペッパーはサーバー側の秘密(ソルトとは異なり、データベースには保存されません)です。ハッシュ化の前にパスワードに追加されます:hash(pepper + salt + password)。攻撃者がデータベースを盗んでも、ペッパーがなければパスワードを解読できません。
まとめ
暗号ハッシュ関数はインターネット全体のセキュリティ、完整性、信頼の基盤です。数学的特性から実際の脆弱性まで理解することで、真に安全なシステムを構築できます。
主要なポイント:
- SHA-256とSHA-512は汎用ハッシュの第一選択
- MD5とSHA-1は暗号用途では破られている
- パスワードには常にbcrypt、scrypt、またはArgon2を使用する
- 整合性だけでなく認証が必要な場合はHMACを使用する
- ソルトはレインボーテーブルを無効化する;bcryptとArgon2は自動的に行う
Tool3M ハッシュジェネレーターを使えば、インストール不要でブラウザ上で直接SHA-256、SHA-512、MD5などのハッシュを素早く計算できます。