NET // communication networks
LESSON 38 / 発展編

ハッシュ関数・MAC ─ 完全性を担う暗号プリミティブ

暗号通信のもう一つの柱は 「データが改ざんされていないこと」を保証する 完全性(integrity)。これを支えるのが ハッシュ関数MAC(Message Authentication Code) です。SHA-256 でファイルの fingerprint を取る、HMAC で API リクエストを認証する、bcrypt でパスワードを保管する、git でコミットを識別する ─ 現代のあらゆる場面でハッシュは登場します。本講では構造(Merkle-Damgård と Sponge)、衝突耐性、HMAC・CMAC・Poly1305 の使い分けを踏み込んで学びます。

学習目標

本講を終えると、以下を達成できるようになります。

本講は 基礎 暗号化第36回 共通鍵暗号(MAC では共有鍵概念を使うため)、第37回 公開鍵暗号 を前提とします。次回 第39回 ディジタル署名 では ハッシュ + 公開鍵 の合わせ技として、本講と前回の知識が両方使われます。

このレッスンの目次

01 ハッシュ関数とは 任意長 → 固定長の一方向関数。fingerprint を取る… 02 求められる 3 性質 原像耐性・第二原像耐性・衝突耐性… 03 構造:MD と Sponge SHA-2 と SHA-3 の根本的な設計の違い… 04 SHA-2 / SHA-3 主要ファミリの位置づけ。なぜ SHA-1 はダメか… 05 MAC 鍵付きハッシュ。HMAC・CMAC・Poly1305… 06 実用ケース パスワード・git・ブロックチェーン・JWT… 07 OpenSSL ハンズオン openssl dgst でハッシュ・HMAC を回す… 08 まとめと用語 本講の重要語句を整理 09 確認問題 5 問で理解度をチェック

ハッシュ関数とは ─ 任意長 → 固定長の一方向関数

暗号学的ハッシュ関数 は、任意長のデータを固定長の値(ハッシュ値・ダイジェスト・フィンガープリント)に圧縮する関数です。1 ビットでも入力が違えば出力は 全く異なるランダムに見える値 になり、出力から入力を復元するのは事実上不可能。これがあらゆる場面で「データの fingerprint」として使われる理由です。
POINT 暗号学的ハッシュ関数の基本性質:
・入力:任意長(数バイト〜数 GB)
・出力:固定長(SHA-256 なら 256 bit = 32 バイト = 64 hex 文字)
決定論的:同じ入力 → 同じハッシュ
雪崩効果:入力 1 ビット変化で出力の約半分が変わる
一方向:出力から入力を求めるのは不可能

暗号化と違い 鍵を使わない(誰でも同じハッシュを計算できる)。だから「秘密」は守らないが「同一性」を証明できる。

同じ入力からは同じハッシュ

$ echo -n "hello" | sha256sum
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824  -

$ echo -n "hello" | sha256sum
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824  -
# ↑ 何度やっても同じ結果

$ echo -n "Hello" | sha256sum
185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969  -
# ↑ "h" → "H" の 1 ビット差で完全に違う出力

ハッシュ関数の役割イメージ

任意長の入力 → 固定長 256 bit の出力 "hi" "the quick brown fox..." 10 GB の動画ファイル SHA-256 8f434346648f6b96df... 256 bit (32 バイト, 64 hex) 9c56cc51b374c3ba18... 256 bit (32 バイト, 64 hex) 2bc9b3d3d8f9c4a17e... 256 bit (32 バイト, 64 hex)

図の見方:入力サイズに関わらず、出力は固定長の 256 bit(SHA-256 の場合)。これを使えば「2 つのファイルが同じか?」は元データを比較せず、ハッシュ 32 バイトを比較するだけで済む。

確認: 暗号学的ハッシュ関数が「一方向(one-way)」であるとは、具体的にどういう性質か?

正解:B。一方向性は 「H から M を逆算できない」(原像計算困難性)を指す。出力は固定長で決定的(A・C)、入力差分が出力に拡散する(D、雪崩効果)、これらは別の重要性質。一方向性が崩れると、パスワードハッシュから元のパスワードが復元できてしまうので、もっとも基本かつ重要な性質。

求められる 3 つの安全性

暗号学的ハッシュ関数が満たすべき安全性は、強度順に 3 段階 あります。これらを満たさないハッシュは、用途によっては危険になります。
POINT ハッシュの 3 安全性:
1. 原像耐性(preimage resistance) ─ ハッシュ値 h から x(=H(x)=h)を求めるのが困難
2. 第二原像耐性(second preimage resistance) ─ 与えられた x に対し、H(x) = H(x') となる別の x' を求めるのが困難
3. 衝突耐性(collision resistance)任意の 2 つの x ≠ x' で H(x) = H(x') となる対を見つけるのが困難

強度の階層

性質難易度(古典コンピュータ)用途
原像耐性2^n(鍵長 n bit と同じ)パスワードハッシュ
第二原像耐性2^nファイル整合性
衝突耐性2^(n/2)(誕生日攻撃)ディジタル署名

衝突耐性が 2^(n/2) なのは 誕生日のパラドックス から来ています。23 人の集まりで誰かと誰かの誕生日が一致する確率は 50% を超える ─ 同じ理由で、2^128 の入力を試せば 2^256 個のハッシュ空間でも衝突が見つかる可能性が高い。だから衝突耐性 128 bit を確保するには、ハッシュ長 256 bit が必要(SHA-256)。

SHA-1 はなぜ廃止されたか

SHA-1(160 bit 出力)は理論上 2^80 で衝突発見可能でしたが、長らく現実的攻撃は不可能とされていました。しかし 2017 年 2 月、Google と CWI Amsterdam が「SHAttered」攻撃 で SHA-1 衝突の実例を発表(同じ SHA-1 を持つ 2 つの異なる PDF ファイルを生成)。この時点で SHA-1 は 署名・証明書用途で禁止。git も SHA-256 への移行を進行中。

もっと詳しく:衝突耐性が破られると何が起きるか

例:CA(認証局)が「example.com 用の証明書」に SHA-1 で署名するとき、攻撃者が「example.com 用と 同じハッシュを持つ 別の証明書(attacker.com 用や 偽の中間 CA)を作成可能なら、署名がそのまま偽証明書にも有効に。これが起こると 偽サイトが完全に正規の HTTPS として機能 してしまう。SHA-1 廃止が急がれた理由はここ。

確認: 「衝突耐性」と「2 番目原像耐性」の違いを最も適切に説明しているのはどれか。

正解:C。衝突耐性の方が攻撃者の自由度が高く、誕生日パラドックスの効果でビット長の半分の強度しか持たない(SHA-256 で 128 bit 強度)。2 番目原像耐性は M が固定なので「特定の M に衝突する M' を見つける」必要があり、ほぼ全数探索の困難さ(256 bit 強度)。証明書偽造は前者が崩れると現実化する ─ MD5 でこれが実証されたため廃止された。

内部構造 ─ Merkle-Damgård と Sponge

暗号ハッシュの内部設計には大きく 2 つの流派 があります。SHA-2 までは Merkle-Damgård 構造、SHA-3 で採用された Sponge 構造です。両者は同じ目的を異なる方法で達成します。

Merkle-Damgård 構造(SHA-1, SHA-2)

入力を固定長ブロック(SHA-256 なら 512 bit)に分割し、各ブロックを順次「圧縮関数」で混ぜていく方式。MD5、SHA-1、SHA-2 がすべてこの構造です。

Merkle-Damgård 構造(SHA-2 系) IV 初期値 圧縮 f M_1 (512b) 圧縮 f M_2 (512b) 圧縮 f M_3 (512b) 圧縮 f M_n + len H(M) 入力をブロックに分けて、IV から始めて圧縮関数 f で順次混ぜる

図の見方:メッセージを 512 bit ブロックに分割。各ブロックを「前の状態 + 現在のブロック → 新しい状態」と圧縮関数 f で処理し、最後の状態をハッシュ値として出力。実装が単純で、長い間標準だった構造。length extension attack(下記)という弱点がある。

Sponge 構造(SHA-3 / Keccak)

2012 年に SHA-3 として採用された Keccak は Sponge(スポンジ) 構造。「吸収(absorb)」と「絞り出し(squeeze)」の 2 段階で動きます。Merkle-Damgård とは設計思想が根本から違う。

Merkle-Damgård(SHA-2)

  • 長く使われ実績豊富
  • ハードウェア実装が成熟
  • length extension attack 脆弱性(後述)
  • 並列処理が難しい

Sponge(SHA-3, BLAKE2/3)

  • length extension に耐性あり
  • 任意長出力(SHAKE)が可能
  • 並列処理しやすい
  • 2012 年以降の新しい設計
もっと詳しく:length extension attack

SHA-256 の出力 H(M) と M の長さが分かっていれば、攻撃者は M を知らなくても H(M ∥ padding ∥ M') を計算できる。これが MAC に SHA-256 をそのまま使うと危険な理由(対策が HMAC。後述)。SHA-3 は Sponge 構造のため最初からこの問題がない。

確認: SHA-3(Keccak)が「長さ拡張攻撃を構造的に持たない」のはなぜか?

正解:B。Merkle-Damgård(SHA-2)は最終チェーン値をそのままハッシュ値として返すため、攻撃者は H から内部状態を知って計算を継続できる。Sponge は r(rate) bit のみ出力し c(capacity) bit は隠蔽 するので、攻撃者は次の状態を再現できない。これが SHA-3 が そのまま MAC に使える(KMAC として標準化)一方、SHA-2 では HMAC で包む必要がある根本理由。

主要なハッシュ関数ファミリ

歴史的に多くのハッシュ関数が標準化・廃止されてきました。現代で使うべきもの、避けるべきものを整理しておきましょう。

主要ハッシュ関数の比較

関数出力長構造状態主な用途
MD5128 bitMerkle-Damgård破られている(廃止)レガシー(チェックサム程度なら可)
SHA-1160 bitMerkle-Damgård破られている(2017 SHAttered)git(移行中)以外は禁止
SHA-256256 bitMerkle-Damgård安全現代の主力(TLS, ブロックチェーン, git の次世代)
SHA-384 / SHA-512384/512 bitMerkle-Damgård安全高セキュリティ・64 bit CPU で速い
SHA-3 / Keccak224〜512 bitSponge安全SHA-2 の代替。長期保険
BLAKE2128〜512 bitBLAKE2 系安全高速。Argon2 内部、WireGuard が採用
BLAKE3任意Merkle ツリー + ChaCha安全非常に高速・並列処理可。新しい標準候補

「とりあえず SHA-256」のススメ

2024 年現在、ほとんどの用途で SHA-256 が安全かつ広く実装されており、第一選択です。長期保険を取るなら SHA-3 を併用。速度重視(ファイルチェック、コンテンツアドレス)なら BLAKE3 が有力。MD5・SHA-1 は新規採用しないこと。

MAC ─ 鍵付きハッシュで認証する

ハッシュ関数だけでは 「データが改ざんされていないこと」は分かるが、「誰が書いたか」は分からない。誰でもメッセージとそのハッシュをセットで偽造できるからです。これに 共有秘密鍵 を組み合わせて、「秘密鍵を持っている人だけが計算できる検証タグ」を作るのが MAC(Message Authentication Code) です。
POINT MAC の役割:
・送信側:tag = MAC(K, message)
・受信側:同じ K で MAC(K, received_msg) == received_tag を確認
鍵 K を知らない第三者は正しい tag を作れない → 改ざん検知 + 認証

主な MAC 方式:
HMAC(RFC 2104) ─ ハッシュベース。HMAC-SHA256 が標準
CMAC(NIST SP 800-38B) ─ AES ブロック暗号ベース
Poly1305 ─ 高速。ChaCha20-Poly1305 として AEAD で使用

MAC と署名の違い

性質MACディジタル署名(第39回)
共有秘密鍵 1 つ秘密鍵(署名) + 公開鍵(検証)
検証可能な相手鍵を共有している 1 人だけ公開鍵を知っている誰でも
否認防止不可(双方が同じ鍵で作れる)可能
速度非常に高速遅い(公開鍵演算)
用途API 認証、TLS 内部証明書、コードサイニング

HMAC ─ 普通のハッシュを安全な MAC にする工夫

HMAC は SHA-256 などの普通のハッシュ関数を 「鍵 + メッセージ」を安全に混ぜる手順 で MAC 化したもの。素朴に H(K ∥ message) とするだけだと length extension attack が成立してしまうため、2 段階のハッシュ で防ぎます。

HMAC(K, m) = H( (K' ⊕ opad) ∥ H( (K' ⊕ ipad) ∥ m ) )

ここで:
  K'   = 鍵 K(短ければゼロパディング、長ければハッシュで縮める)
  ipad = 0x36 を繰り返したバイト列
  opad = 0x5C を繰り返したバイト列
  H    = ハッシュ関数(SHA-256 等)
  ∥    = 連結

つまり「鍵をパディングしてから内側ハッシュ → 外側ハッシュ」を 2 重に通す。
これにより length extension attack が成立しない。

HMAC の使われ方

Q. 自前で「ハッシュ関数を使った認証コード」を作るとして、もっとも素直そうな tag = SHA256(secret ∥ message)(秘密鍵を頭にくっつけて SHA-256 をかける)は なぜ安全でないのか?

実用ケース ─ ハッシュは至る所に

ハッシュ関数の用途は驚くほど多彩。日常的に触れているシステムのほとんどで内部的に使われています。

① ファイル整合性チェック

大きなソフトを公式サイトからダウンロードしたら、添付の SHA-256 値 と自分で計算した値を突き合わせる。途中でファイルが壊れたり改ざんされていないかを 1 行で検証できる。

$ sha256sum ubuntu-22.04-desktop-amd64.iso
b98dac940a82b110e6265ca78d1320f1f7103861e922247a4cdf6f17  ubuntu...

# 公式の値と比較

② パスワード保管

サーバはユーザのパスワードを そのまま保存しない。ハッシュだけ保存し、ログイン時に「入力されたパスワードのハッシュ == 保存されているハッシュ」を比較。サーバが侵害されてもパスワードそのものは露出しない。

ただし 素の SHA-256 だけでは不十分。GPU で 1 秒に何十億回もハッシュ計算できるので、辞書攻撃で破られる。対策は 意図的に遅いハッシュ関数:

③ git のコミット ID

git の commit a3f4b2... という ID は そのコミットの内容(変更・親コミット・著者・メッセージ)を全部結合したものの SHA-1 ハッシュ。コンテンツが 1 文字でも違えば全く違う ID になる ─ git の整合性の根幹。git は SHA-256 への移行を進行中。

④ ブロックチェーン

Bitcoin の各ブロックは 「前のブロックのハッシュ + 取引データ + nonce」を SHA-256 した値 を持つ。マイナーは「特定の条件(先頭ゼロが多い)を満たすハッシュ」を見つけるため nonce を試行錯誤 ─ これが Proof of Work。ハッシュの一方向性と衝突耐性が PoW の安全性そのもの

⑤ JWT(JSON Web Token)

JWT の HS256 アルゴリズムは HMAC-SHA256。header.payload.signature という 3 部構成で、signature は HMAC-SHA256(secret, header + "." + payload)。受け取り側は同じ secret で再計算して検証 ─ 改ざんなしと送信元の認証を 1 つで実現。

⑥ コンテンツアドレス指定ストレージ

IPFS・Docker イメージ・nix パッケージマネージャは「ファイル名ではなく ハッシュ値 でファイルを識別」する。同じハッシュなら同じ内容、自動的にデデュプリケーション。

つながる知識: ハッシュは「機密性なし、完全性あり」の暗号プリミティブ。鍵を必要とせず誰でも計算できるので「秘密」は守らないが、「同一性」を強力に保証する。これが多様な用途に使える理由です。

確認: Web サービスでユーザのパスワードを保存するとき、なぜ「単純に SHA-256 でハッシュ化」だけでは不十分なのか?

正解:D。汎用ハッシュは「速い」のが長所だが、パスワード保管では 致命的弱点。bcrypt / scrypt / Argon2 のような 計算的に重く・メモリも食う ハッシュ関数(KDF)を、ユーザごとの salt と組み合わせて使うのが現代の正解。Argon2 は 2015 年の Password Hashing Competition の優勝アルゴリズムで、OWASP の推奨。「正しい道具を正しい目的に」の典型例。

OpenSSL ハンズオン

OpenSSL でハッシュと HMAC を実際に計算してみましょう。

1. 各種ハッシュ関数

# SHA-256
echo -n "hello world" | openssl dgst -sha256
# (stdin)= b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9

# SHA-512
echo -n "hello world" | openssl dgst -sha512

# SHA-3
echo -n "hello world" | openssl dgst -sha3-256

# BLAKE2
echo -n "hello world" | openssl dgst -blake2b512

# ファイル全体のハッシュ
openssl dgst -sha256 myfile.tar.gz

# 利用可能なアルゴリズムを列挙
openssl dgst -list

2. HMAC の計算

# HMAC-SHA256(鍵を文字列で指定)
echo -n "important message" | \
    openssl dgst -sha256 -hmac "supersecretkey"

# HMAC-SHA256(鍵をバイナリで指定)
KEY_HEX=$(openssl rand -hex 32)
echo "KEY=$KEY_HEX"
echo -n "important message" | \
    openssl dgst -sha256 -mac HMAC -macopt "hexkey:$KEY_HEX"

3. パスワードハッシュ(Argon2 を Python で)

# pip install argon2-cffi
from argon2 import PasswordHasher

ph = PasswordHasher()

# 保存時:ハッシュを生成(salt とパラメータも含む文字列)
hashed = ph.hash("user-password-123")
print(hashed)
# → $argon2id$v=19$m=65536,t=3,p=4$xxx$yyy

# ログイン時:検証
try:
    ph.verify(hashed, "user-password-123")
    print("OK")
except Exception:
    print("Wrong password")

4. JWT(HS256)を Python で

# pip install pyjwt
import jwt

secret = "my-shared-secret"
payload = {"user_id": 42, "exp": 1735689600}

# 生成
token = jwt.encode(payload, secret, algorithm="HS256")
print(token)
# → eyJhbGciOi...

# 検証(secret が違うと InvalidSignatureError)
decoded = jwt.decode(token, secret, algorithms=["HS256"])
print(decoded)
# → {'user_id': 42, 'exp': 1735689600}

まとめと用語チェック

SUMMARY 1. ハッシュ関数 = 任意長 → 固定長の決定論的な一方向関数
2. 安全性は 原像耐性 / 第二原像耐性 / 衝突耐性 の 3 つ。衝突は誕生日攻撃で 2^(n/2)
3. 構造は Merkle-Damgård(SHA-2)と Sponge(SHA-3)の 2 系統
4. SHA-1 は 2017 年衝突実例で事実上廃止、現代標準は SHA-256。長期は SHA-3
5. MAC = ハッシュ + 共有秘密鍵で改ざん検知 + 認証。HMAC-SHA256 が標準
6. パスワードは 遅いハッシュ(bcrypt/scrypt/Argon2)で保管
7. 用途は無限:整合性・パスワード・git・ブロックチェーン・JWT・コンテンツアドレス

用語チェック

用語1行説明
ハッシュ関数 / ダイジェスト任意長 → 固定長の暗号プリミティブ。fingerprint 用
原像耐性H(x) = h を満たす x を求めるのが困難
第二原像耐性x が与えられた時、H(x) = H(x') の x' を求めるのが困難
衝突耐性任意の 2 つで H(x) = H(x') の対を見つけるのが困難
誕生日攻撃衝突探索の難易度が 2^(n/2)(出力 n bit に対し)
Merkle-DamgårdSHA-2 までの構造。圧縮関数で順次混ぜる
Sponge / KeccakSHA-3 の構造。吸収と絞り出しの 2 段階
length extension attackMD 構造の弱点。SHA-2 を素朴に MAC に使うと危険
SHA-256 / SHA-3現代の主力ハッシュ。256 bit 出力、衝突耐性 128 bit
BLAKE2 / BLAKE3高速なモダンハッシュ。WireGuard 等で採用
MAC(Message Auth Code)共有鍵で改ざん検知 + 認証するタグ
HMACRFC 2104。ハッシュ関数を 2 段階で MAC 化
CMACAES ベースの MAC。NIST SP 800-38B
Poly1305高速 MAC。ChaCha20-Poly1305 で使用
bcrypt / scrypt / Argon2パスワード保管用の遅いハッシュ
NEXT: 次回 第39回 ディジタル署名 では、本講のハッシュと前回(第37回)の公開鍵暗号を組み合わせた ディジタル署名 を扱います。RSA-PSS、ECDSA、Ed25519、X.509 証明書での使われ方、コードサイニングまで踏み込みます。

確認問題

問1. 暗号学的ハッシュ関数の 衝突耐性について、出力 n bit のハッシュで衝突を見つける期待計算量はどれか。

次の選択肢から最も適切なものを選択してください。
A. 2^n
B. 2^(n/2)
C. n
D. n^2
正解:B
誕生日のパラドックスにより、2^(n/2) 個の入力を試せば衝突が見つかる確率が高い。これは「ハッシュ長 n bit でも、衝突耐性は実質 n/2 bit」と読める。SHA-256 が 256 bit 出力を持つのは衝突耐性 128 bit を確保するため。

問2. SHA-1 が現代では使用されなくなった主な理由は何か。

次の選択肢から最も適切なものを選択してください。
A. 計算速度が遅すぎるから
B. パスワード保管に不向きだから
C. ハッシュ値が短すぎるから
D. 2017 年に衝突実例(SHAttered)が公開され、衝突耐性が破られたから
正解:D
Google と CWI が「SHA-1 が同じになる 2 つの異なる PDF」を実際に生成して公開したことで、SHA-1 の衝突耐性は理論ではなく 現実の脅威 となった。これを受け各機関は SHA-1 を署名・証明書から廃止。git のような整合性用途では現役だが、新規署名には使えない。

問3. MAC とディジタル署名の 本質的な違い として、最も適切なものはどれか。

次の選択肢から最も適切なものを選択してください。
A. MAC のほうが鍵長が長い
B. ディジタル署名のほうが処理が速い
C. ディジタル署名は 否認防止 ができるが、MAC はできない(2 者が同じ鍵で作れるため)
D. MAC はハッシュを使わない
正解:C
MAC は共有秘密鍵を持つ 2 者ならどちらでも計算できる → 「相手が作ったか自分が作ったか」を区別できないので 否認防止は不可能。ディジタル署名は秘密鍵が一人しか持たないので、署名者本人が作ったと 客観的に証明可能。これが法的有効性を持つ署名に MAC ではなくディジタル署名が使われる理由。

問4. パスワードを保管するときに、SHA-256 をそのまま使うのではなく bcrypt や Argon2 を使う理由として、最も適切なものはどれか。

次の選択肢から最も適切なものを選択してください。
A. SHA-256 は GPU で高速計算できるため、辞書攻撃に対する防御として「意図的に遅い」ハッシュが必要
B. SHA-256 はハッシュ衝突がよく起きるから
C. bcrypt のほうが計算が速いから
D. SHA-256 は鍵長が短いから
正解:A
パスワードは元エントロピーが低いので、攻撃者は辞書を順次ハッシュ化して照合する。SHA-256 は GPU で 1 秒に数十億回 も計算できてしまう。bcrypt・scrypt・Argon2 は 意図的に時間とメモリを消費 する設計で、1 秒に数百回程度しか試せない → 辞書攻撃のコストを実用不可能なレベルまで上げる。

問5. HMAC が単純な H(K ∥ message)(鍵とメッセージを連結してハッシュ)よりも 2 段階のハッシュ計算 を採用している理由として、最も適切なものはどれか。

次の選択肢から最も適切なものを選択してください。
A. 計算速度を上げるため
B. SHA-2 系の length extension attack を防ぐため
C. 並列化のため
D. 量子計算耐性のため
正解:B
Merkle-Damgård 構造の SHA-2 系は、H(K ∥ M) と M の長さが分かれば K を知らなくても H(K ∥ M ∥ padding ∥ M') を計算できる(length extension attack)。HMAC は ipad/opad で 2 段階に挟むことでこの攻撃を構造的に排除している。SHA-3 は Sponge 構造でこの問題がないので「KMAC」のような単純な MAC が定義できる。
← PREV
第37回 公開鍵暗号(RSA・ECC)
NEXT →
第39回 ディジタル署名