ハッシュ関数とは、どんな長さのデータを入れても決まった長さの値(ハッシュ値)を返す関数です。SHA-256 なら、1 文字でも 1 ギガバイトのファイルでも、常に 256 ビット(16進64文字)の値になります。重要なのは、同じ入力からは必ず同じ値が出る(決定的)一方で、ハッシュ値から元の入力には戻せない(一方向)こと。本記事では SHA-256 を例に、ハッシュ関数の仕組みと性質、暗号化との違い、そしてパスワード保存や改ざん検知での正しい使い方を整理します。
1. ハッシュ関数とは — 任意長を固定長に、一方向で決定的
ハッシュ関数は、任意の長さの入力(メッセージ)を受け取り、固定長のビット列(ハッシュ値、ダイジェスト)に変換する関数です。代表例の SHA-256 は、入力が何であっても出力は常に 256 ビットになります。基本的な性質は次の 3 つです。
- 任意長 → 固定長:入力の長さに関わらず、出力の長さは一定(SHA-256 は 256 ビット = 16進で 64 文字)。
- 決定的:同じ入力なら、いつ・どこで計算しても必ず同じハッシュ値になります。だから照合に使えます。
- 一方向:入力 → ハッシュ値は簡単に計算できますが、ハッシュ値 → 入力を求める手続きは存在しません。情報が圧縮されて失われるため、原理的に「復号」できないのです。
同じ入力から同じ 64 文字が必ず得られることを、具体例で確認します。
SHA-256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
2. ハッシュ関数の性質 — 衝突耐性・原像計算困難性・なだれ効果
暗号学的に安全なハッシュ関数は、単に固定長を返すだけでなく、次の性質を満たすよう設計されています。
- 原像計算困難性(一方向性):あるハッシュ値
hが与えられたとき、hash(x) = hとなる入力xを見つけるのが計算上きわめて困難であること。 - 第二原像計算困難性:ある入力
xが与えられたとき、hash(x) = hash(y)となる別の入力y(y ≠ x)を見つけるのが困難であること。 - 衝突耐性:
hash(x) = hash(y)となる異なる入力ペア(x, y)を見つけるのが困難であること。出力は有限なので衝突は数学的には必ず存在しますが、現実的な計算量では見つけられないことが要件です。
もう一つ重要なのが なだれ効果(avalanche effect) です。入力をほんの 1 ビット変えただけで、出力のビットの約半分が変わり、結果はまったく別物に見えます。次の例を比べてみてください(末尾の . を 1 つ足しただけ)。
| 入力 | SHA-256(先頭部分) |
|---|---|
The quick brown fox | 5cac4f98… とまったく異なる値 |
The quick brown fox. | 7d38b56b… と別物に変化 |
このため、ハッシュ値を見ても元データのどの部分が似ているかは一切推測できません。1 文字の改ざんでもハッシュは劇的に変わるので、改ざん検知に向いています。
3. 暗号化との違い — ハッシュは復号できない
ハッシュと暗号化はしばしば混同されますが、目的も可逆性もまったく異なります。最大の違いは「元に戻せるかどうか」です。
| 観点 | ハッシュ(SHA-256 など) | 暗号化(AES など) |
|---|---|---|
| 目的 | 指紋・照合・改ざん検知 | 秘密の保持(読めなくする) |
| 可逆性 | 不可逆(復号できない) | 可逆(鍵があれば復号できる) |
| 鍵 | 不要 | 必要(暗号化/復号の鍵) |
| 出力の長さ | 固定長 | おおむね入力に比例 |
| 典型例 | パスワード照合・チェックサム | 通信・保存データの暗号化 |
暗号化は、正しい鍵を持つ人が後で平文に戻すことを前提とした仕組みです。一方、ハッシュには「戻す」操作がそもそも定義されていません。「ハッシュ化したパスワードを復号する」という表現は誤りで、実際にできるのは、入力したパスワードをもう一度ハッシュ化して、保存済みの値と一致するか比べることだけです。
4. 代表的なアルゴリズム — MD5/SHA-1 は非推奨、SHA-256/512 推奨
ハッシュ関数にはいくつもの種類があり、安全性が確認されているものとそうでないものがあります。
| アルゴリズム | 出力長 | 推奨度・備考 |
|---|---|---|
| MD5 | 128 ビット | 非推奨。衝突が容易に作れる。暗号用途は不可 |
| SHA-1 | 160 ビット | 非推奨。2017 年に現実的な衝突が公表(SHAttered) |
| SHA-256 | 256 ビット | 推奨。SHA-2 系。現在の標準的な選択肢 |
| SHA-512 | 512 ビット | 推奨。SHA-2 系。64bit 環境で高速なことが多い |
| SHA-3 | 可変 | 推奨。SHA-2 とは内部構造が異なる新世代 |
なお、攻撃者がいない前提の単純な重複検出やキャッシュキーなど、非暗号用途であれば MD5 でも直ちに問題にはなりません。ただし混乱や流用リスクを避けるため、新規実装では避けるのが無難です。
5. 用途 — パスワード保存・改ざん検知・データ識別
ハッシュ関数は身近なところで広く使われています。代表的な用途を、正しい使い方とともに挙げます。
- パスワード保存:平文ではなくハッシュを保存し、ログイン時に入力を同じ手順でハッシュ化して照合します。ただし生の SHA-256 では不十分で、利用者ごとのソルトを付け、bcrypt・scrypt・Argon2 のような専用のパスワードハッシュ関数(鍵導出関数)を使うのが鉄則です(次章参照)。
- 改ざん検知・チェックサム:ファイルやメッセージのハッシュを公開しておけば、受け取った側が再計算して一致を確認でき、転送中の破損や改ざんに気づけます。配布物の
SHA-256SUMSはこの用途です。 - データ識別(コンテンツアドレス):内容そのものから一意な ID を作れます。Git のコミットや、重複排除ストレージ、キャッシュキーなどで使われます。
- 署名・HMAC の内部:デジタル署名やメッセージ認証(HMAC)も内部でハッシュを使い、データの完全性と真正性を担保します。
6. 注意点 — 生 SHA でのパスワード保存はNG、ソルトとストレッチング
最後に、最も間違えやすいポイントを押さえます。パスワードを生の SHA-256 でハッシュ化して保存してはいけません。理由は、SHA-256 が高速に計算できるよう作られているからです。速さは通常は利点ですが、攻撃者にとっては「毎秒膨大な候補を試せる」という意味で有利に働きます。
ソルト(salt)
ソルトは、ハッシュ化の前にパスワードへ付け足す、利用者ごとにユニークなランダム値です。これにより次の攻撃を防ぎます。
- レインボーテーブル対策:事前計算済みのハッシュ対応表が使えなくなります。
- 同一パスワードの一括判別を防ぐ:同じパスワードでも利用者ごとに別のハッシュになり、漏えい時に「同じパスワードの人」をまとめて狙えなくなります。
ストレッチング(key stretching)
ストレッチングは、ハッシュ計算を何千回〜何十万回も繰り返したり、メモリを大量に使わせたりして、1 回の検証をわざと重くする手法です。正規利用者には体感できない遅さでも、総当たりする攻撃者には大きな壁になります。これを安全に実装したのが bcrypt・scrypt・Argon2 といったパスワード専用の関数です。
よくある質問(FAQ)
ハッシュと暗号化は何が違うのですか?
最大の違いは「元に戻せるかどうか」です。暗号化は鍵を使って平文を暗号文に変換し、正しい鍵があれば復号して元の平文に戻せます(可逆)。一方、ハッシュ関数は入力から固定長の値を計算するだけで、ハッシュ値から元の入力を復元する手続きは存在しません(一方向・不可逆)。したがって「パスワードをハッシュ化して保存する」ことはできても「ハッシュ化したデータを復号する」ことは原理的にできません。秘密にして後で読み戻したいデータには暗号化を、改ざん検知や照合のための指紋にはハッシュを使います。
SHA-1 や MD5 はまだ安全に使えますか?
署名・証明書・改ざん検知など、衝突耐性が必要な用途では使ってはいけません。MD5 は早くから、SHA-1 も 2017 年に現実的な衝突(異なる入力で同じハッシュ値になる例)が公表されており、いずれも非推奨です。新規の用途では SHA-256 や SHA-512(SHA-2 系)、または SHA-3 を使ってください。なお、暗号目的でない単純な重複検出やキャッシュキーなど、攻撃者がいない前提の用途であれば MD5 を使い続けても直ちに危険ではありませんが、混乱を避けるため新規実装では避けるのが無難です。
パスワードの保存は SHA-256 だけでよいですか?
いいえ。SHA-256 は高速に計算できるよう設計されており、その速さが攻撃者にとって有利に働くため、パスワードを生の SHA-256 でハッシュ化して保存するのは不適切です。攻撃者は GPU で毎秒膨大な候補を試せますし、ソルトがなければレインボーテーブルや同一パスワードの一括判別も可能です。パスワードには、利用者ごとにユニークなソルトを付け、bcrypt・scrypt・Argon2 といった計算コストを調整できる専用のパスワードハッシュ関数(鍵導出関数)を使ってください。これらは意図的に遅く、総当たり攻撃を困難にします。