ハッシュ関数 - 暗号化全般

暗号化全般 | IT用語集

ハッシュ関数とは

ハッシュ関数(Hash Function)は、任意の長さのデータを固定長のハッシュ値(ダイジェスト)に変換する一方向関数です。暗号学的ハッシュ関数は、以下の特性を持ちます:

  • 一方向性:ハッシュ値から元データを逆算できない
  • 衝突耐性:異なるデータから同じハッシュ値を生成することが困難
  • 雪崩効果:入力の微小な変化で出力が大きく変化
  • 高速:計算が効率的

具体的な数値でイメージすると、SHA-256は入力データが1バイトの短い文字列であっても数GBの動画ファイルであっても、常に256ビット(32バイト、16進数表記で64文字)の固定長出力を返します。同様にSHA-1は160ビット(40文字)、MD5は128ビット(32文字)の固定長です。出力長が長いほど計算コストはわずかに増えますが、現代のCPU・GPUではSHA-256程度の演算負荷は実用上ほとんど気にする必要がありません。

「暗号学的ハッシュ関数」という呼び方が示す通り、単なる「要約」や「圧縮」ではなく、一方向性・衝突耐性・雪崩効果に加えて「第二原像耐性(あるハッシュ値と同じ結果になる別の入力を見つけにくいこと)」という暗号学的な安全性要件を満たす必要があります。CRC32のような非暗号学的ハッシュとの違いは、後述の「混同されやすい用語・類似技術との違い」セクションで詳しく解説します。

ハッシュ関数の仕組み・詳細解説

SHA-2ファミリーの内部構造(Merkle-Damgard構成)

SHA-256は「Merkle-Damgard構成」と呼ばれる設計に基づいています。処理の流れは次の通りです。まず入力メッセージの末尾に「1」ビットとパディング用の0を付加し、さらにメッセージの元の長さ(ビット数)を付け足して、全体を512ビットの倍数に揃えます(これを「Merkle-Damgard強化」と呼びます)。次にメッセージを512ビットのブロックに分割し、各ブロックを圧縮関数に順番に通していきます。圧縮関数の内部では、a〜hという8つの32ビット作業変数(合計256ビットの内部状態)に対して、Ch・Maj・Σ0・Σ1といった論理演算とビット回転を組み合わせた処理を64ラウンド繰り返し、前のブロックの出力を次のブロックの入力として連鎖させます。最終ブロックを処理し終えた時点の8つの作業変数を連結したものが256ビットのハッシュ値です。SHA-512はブロック長が1024ビット、作業変数が64ビット×8個という違いはありますが、基本構造は同じです。

SHA-3(Keccak)のスポンジ構造

SHA-3は2015年にNIST(米国立標準技術研究所)がFIPS 202として標準化した規格で、内部設計はSHA-2とはまったく異なる「スポンジ構造」を採用しています。1600ビットの内部状態を持ち、メッセージを「レート(rate)」と呼ばれる部分に順次XORで吸収させていく「吸収(absorb)フェーズ」と、そこから必要な長さのハッシュ値を絞り出す「搾り出し(squeeze)フェーズ」の2段階で構成されます。攪拌には「Keccak-f[1600]」という24ラウンドの置換関数が使われ、レート以外の「容量(capacity)」部分のビット数が安全性の強度を決めます(例:SHA3-256はレート1088ビット・容量512ビット)。この設計はSHA-2の内部構造と数学的に独立しているため、仮に将来SHA-2ファミリーに未知の脆弱性が見つかった場合でもSHA-3は影響を受けにくく、NISTはSHA-3を「SHA-2に対する保険」として標準化しました。

雪崩効果と衝突耐性を支える数学(誕生日のパラドックス)

暗号学的ハッシュ関数の安全性評価では「誕生日のパラドックス(Birthday Paradox)」が重要な考え方になります。n ビットの出力を持つハッシュ関数において、無作為に入力を試して衝突(同じハッシュ値になる2つの異なる入力)を見つけるのに必要な試行回数は、理論上おおよそ2の(n/2)乗程度とされています。したがって256ビット出力のSHA-256は「128ビットの衝突耐性」を持つと評価され、必要な計算量はおよそ2の128乗回に達します。これは現時点で存在するあらゆるコンピュータ資源を動員しても現実的な時間内には到達できない規模とされており、実用上は安全と考えられています。一方、MD5(128ビット出力)は理論上の衝突耐性が64ビット相当にとどまり、さらに設計上の弱点も加わったことで実際に高速な衝突生成が可能になってしまいました。

鍵長・出力長に関する2026年時点のNIST推奨

米NIST(SP 800-131A Rev.2)は、SHA-1についてデジタル署名生成用途では2013年以降の使用を非推奨とし、既存データの検証目的を除いて新規利用を認めていません。新規システムでは、汎用ハッシュとしてSHA-256以上(SHA-224/256/384/512、またはSHA3-224/256/384/512)を使うことが推奨されています。目安として、一般的な用途では128ビット安全性レベル(SHA-256相当)で十分ですが、10年を超える長期保存データや高セキュリティ要件が求められる領域では、192ビット安全性レベル(SHA-384・SHA3-384相当)以上を選ぶ設計が定石とされています。米NSAが公表した「CNSA 2.0」でも、長期的な移行方針としてSHA-384以上の採用が推奨されており、政府・防衛関連システムを中心に段階的な移行が進んでいます。

代表的なハッシュ関数

名前出力長状態
MD5128ビット❌ 非推奨(衝突攻撃可能)
SHA-1160ビット❌ 非推奨(衝突攻撃可能)
SHA-256256ビット✅ 推奨
SHA-512512ビット✅ 推奨(長期保存向け)
SHA-3-256256ビット✅ 推奨
SHA-3-512512ビット✅ 推奨(長期保存向け)
BLAKE3可変✅ 高速・推奨

表中の「非推奨」には具体的な歴史的経緯があります。MD5は2004年に王小雲(Wang Xiaoyun)らの研究チームが実用的な衝突生成手法を発表したことで、暗号学的な安全性への信頼を失いました。SHA-1については2017年、GoogleとCWI Amsterdam(オランダの研究機関)の共同チームが「SHAttered」と名付けた攻撃で、内容の異なる2つのPDFファイルから同一のSHA-1ハッシュ値を生成できることを実証しました。これを契機に主要ブラウザベンダーはSHA-1証明書の信頼を停止し、GitやSubversionなど多くのバージョン管理システムもSHA-256系への移行を進めています。BLAKE3はBLAKE2の後継として2020年に公開された比較的新しいハッシュ関数で、並列処理(SIMD命令やマルチコア)に最適化された内部構造により、単一スレッドでもSHA-256の数倍程度の高速な処理が可能です。RustやZigのエコシステムを中心に、ビルドツールのキャッシュキー生成や大容量ファイルの整合性検証用途で採用が広がっています。

AIエンジニアとしての実体験

機械学習パイプラインでハッシュ関数を多用しています:

import hashlib

# ファイルのSHA-256ハッシュを計算
def file_hash(filepath):
    sha256 = hashlib.sha256()
    with open(filepath, 'rb') as f:
        for chunk in iter(lambda: f.read(4096), b''):
            sha256.update(chunk)
    return sha256.hexdigest()

# モデルファイルの整合性チェック
expected = "abc123..."
actual = file_hash("model.pt")
assert expected == actual, "ファイルが破損しています"

# 重複データの検出
data_hash = hashlib.sha256(data.encode()).hexdigest()

もう一つ実務で頻出するのが、ハッシュ値を使った文字列比較のミスです。認証トークンやAPIリクエストの署名を検証する際、単純な文字列比較演算子(=====)でハッシュ値同士を比較すると、比較処理にかかる時間の差から情報が漏洩する「タイミング攻撃」のリスクがあります。実務では、Node.jsのcrypto.timingSafeEqualのような定数時間比較関数を使うのが定石です。

// Node.jsでのタイミングセーフなハッシュ比較
const crypto = require('crypto');

function safeCompareHash(hexA, hexB) {
  const bufA = Buffer.from(hexA, 'hex');
  const bufB = Buffer.from(hexB, 'hex');
  if (bufA.length !== bufB.length) return false;
  return crypto.timingSafeEqual(bufA, bufB);
}

// ブラウザ側ではWeb Crypto APIのSubtleCryptoでSHA-256を計算できる
async function sha256Hex(text) {
  const data = new TextEncoder().encode(text);
  const digest = await crypto.subtle.digest('SHA-256', data);
  return Array.from(new Uint8Array(digest))
    .map(b => b.toString(16).padStart(2, '0')).join('');
}

用途と注意点

適切な用途

  • ファイル/データの整合性検証:ダウンロードしたファイルの公開ハッシュ値と手元で計算したハッシュ値を突き合わせ、破損や改ざんがないか確認する
  • デジタル署名の一部:署名対象のデータそのものではなく、そのハッシュ値に対して秘密鍵で署名を行うことで、大きなデータでも高速に署名処理ができる
  • ブロックチェーン:各ブロックが前のブロックのハッシュ値を含むことで改ざん検知を行い、Proof of Workのマイニング処理にもハッシュ計算が使われる
  • キャッシュキーの生成:URLやクエリパラメータ、ファイル内容からハッシュ値を計算し、CDNやアプリケーションキャッシュのキーとして利用する
  • 重複データ検出:大量のファイルやレコードの中から完全に同一の内容を持つものを高速に見つけ出す

不適切な用途

  • パスワード保存:SHA-256やSHA-3のような汎用ハッシュ関数は計算速度が速すぎるため、総当たり攻撃(ブルートフォース)に対して脆弱です。Argon2id、bcrypt、scryptといったパスワードハッシュ専用関数(計算コストを意図的に高くした鍵導出関数)を使用すべきです
  • 暗号化の代替:ハッシュは不可逆変換であるため、後で元データを復元する必要がある秘匿情報(クレジットカード番号や個人情報など)の保護にはAESなどの対称暗号を使うべきです

混同されやすい用語・類似技術との違い

暗号化・エンコーディングとの違い

「暗号化」「エンコーディング」「ハッシュ化」は、いずれも入力データを別の形式に変換する処理ですが、目的と可逆性がまったく異なります。暗号化(AES、RSAなど)は鍵を使ってデータを変換し、対応する鍵があれば元のデータに復号できる「可逆」な処理です。エンコーディング(Base64、URLエンコードなど)はデータ形式を変換するだけで暗号学的な安全性の目的はなく、誰でも簡単に元に戻せます。一方ハッシュ化は、意図的に「元に戻せない」ことを設計目標とした一方向変換です。「パスワードを暗号化して保存する」という表現を見かけることがありますが、実務上は不正確で、正しくはハッシュ化(+ソルト)が適切な保存方法です。

チェックサム(CRC32等)との違い

CRC32やAdler32といった「チェックサム」は、通信エラーやファイル破損など偶発的なビット化けを検出する目的で設計された非暗号学的ハッシュです。計算は非常に高速ですが、衝突を意図的に作り出すことが容易で、悪意ある改ざんへの耐性はありません。ZIPファイルの整合性チェックやEthernetフレームのエラー検出には適していますが、デジタル署名や改ざん検知、パスワード保存といったセキュリティ用途には絶対に使用してはいけません。

HMAC・鍵導出関数(KDF)との違い

ハッシュ関数単体には「秘密鍵」の概念がないため、誰でも同じ入力から同じハッシュ値を再計算でき、メッセージ認証(なりすまし防止)には使えません。秘密鍵を組み合わせてメッセージ認証符号(MAC)として使えるようにしたものがHMAC(RFC 2104)です。また、パスワードのようにブルートフォース耐性が要求される用途では、単純なハッシュ関数を1回適用するのではなく、計算コストを意図的に高めた鍵導出関数(KDF)——Argon2id、PBKDF2(RFC 8018)、scrypt——を使うのが実務の定石です。「ハッシュ関数」「HMAC」「KDF」はいずれもハッシュ処理を内部で使いますが、目的と設計は明確に異なります。

トラブル事例と対策

⚠️ MD5/SHA-1の使用継続

リスク:衝突攻撃により偽造が可能

対策:SHA-256以上に移行。Git等のレガシー用途でも注意。

⚠️ ソルトなしのパスワードハッシュとレインボーテーブル攻撃

リスク:ソルト(ランダムな追加データ)を付けずにパスワードをハッシュ化すると、事前に大量の候補文字列とハッシュ値の対応表を計算しておく「レインボーテーブル」を使って高速に逆引きされてしまいます。同じパスワードを使う複数ユーザーのハッシュ値が一致してしまう問題もあります。

対策:Argon2idやbcryptはアルゴリズム自体にソルトの生成・管理が組み込まれているため、標準的な実装をそのまま使うのが安全です。独自にSHA-256などでパスワードをハッシュ化する設計は避け、必ず専用のパスワードハッシュ関数を使ってください。

⚠️ ハッシュ拡張攻撃(Length Extension Attack)

リスク:SHA-256やMD5のようなMerkle-Damgard構造のハッシュ関数で、hash(秘密鍵 || メッセージ)という単純な構成で簡易的なメッセージ認証を作ると、攻撃者が秘密鍵の値を知らなくても、既知のハッシュ値からメッセージを追加した新しいハッシュ値を計算できてしまう場合があります。

対策:メッセージ認証にはハッシュ関数を直接使わず、必ずHMACを使用してください。なお、スポンジ構造を採用するSHA-3(Keccak)はこの種の攻撃を受けにくい設計になっています。

2025-2026年の最新動向

ポスト量子ハッシュの研究が進んでいます。現在のSHA-256は量子コンピュータに対しても比較的安全とされていますが(Groverのアルゴリズムで安全性が半減する程度)、SHA-256の2倍のセキュリティマージンを持つSHA-512や、SHA-3の大きな出力長(SHA3-512)の採用が推奨されるケースが増えています。米NSAが公表した「CNSA 2.0」でも、長期的な移行方針として政府・防衛関連システムを中心にSHA-384以上の採用が推奨されており、段階的な移行スケジュールが示されています。

BLAKE3が高速ハッシュ関数として注目を集めています。BLAKE2の後継で、並列処理に最適化された設計により、SHA-256より数倍程度高速な処理を実現しながらセキュリティレベルを維持しています。Rustコミュニティを中心に採用が広がっており、ビルドツールのキャッシュキー生成や大規模データセットの重複検出用途で使われる例が増えています。

パスワードハッシュの分野では、Argon2idがOWASP Password Storage Cheat Sheetで第一選択として推奨されており、Argon2自体もIETFにより2021年にRFC 9106として正式に標準化され、実装間の相互運用性が確保されました。新規プロジェクトの多くがArgon2idを採用する傾向は2026年時点でも続いています。

よくある質問(FAQ)

Q. ハッシュ関数とは?

任意長のデータを固定長の値(ハッシュ値)に変換する一方向性の関数です。同じ入力からは必ず同じハッシュが生成され、ハッシュから元データを復元できません。

Q. パスワード保存にはどのハッシュ関数を使うべき?

Argon2id(第一選択)、bcrypt、scryptなどのパスワードハッシュ専用関数を使用してください。SHA-256は高速すぎるためパスワード用途には不適切です。

Q. MD5は安全ですか?

MD5は衝突脆弱性が発見されており、セキュリティ用途には使用禁止です。チェックサム目的では使えますが、SHA-256以上の使用を推奨します。

Q. レインボーテーブル攻撃とは何ですか?ソルトでなぜ防げるのですか?

レインボーテーブルとは、大量の候補文字列とそのハッシュ値の対応表をあらかじめ計算しておき、ハッシュ値から元のパスワードを高速に逆引きするための攻撃手法です。ユーザーごとに異なるランダムな「ソルト」をパスワードに付加してからハッシュ化すると、同じパスワードでもユーザーごとに異なるハッシュ値になるため、事前計算されたテーブルが使えなくなります。Argon2idやbcryptはソルトの生成・管理をアルゴリズム内部で自動的に行います。

Q. ハッシュ拡張攻撃とは何ですか?

SHA-256やMD5のようなMerkle-Damgard構造のハッシュ関数を使ってhash(秘密鍵 || メッセージ)という単純な方法でメッセージ認証を作ると、秘密鍵を知らない攻撃者でも既知のハッシュ値から末尾にデータを追加した新しいハッシュ値を計算できてしまう脆弱性です。対策として、メッセージ認証には素のハッシュ関数ではなく必ずHMACを使用します。

関連用語

📝 関連ブログ記事

【2026年最新】OpenSSLの深刻な問題とは?

セキュリティ実装のご相談