暗号化とハッシュ化は、どちらもデータを別の形に変換する処理ですが、セキュリティにおける役割は根本的に異なります。両者を分ける最大の基準は可逆性です。暗号化は正しい鍵を使えばデータを元の状態に復号できます。一方、ハッシュ化には「元に戻す」という概念自体がなく、生成されたハッシュ値から入力データを復元する計算手段は存在しません。
この性質の違いが、暗号化を「通信やストレージの機密性保護」に、ハッシュ化を「パスワード保存や改ざん検知」に振り分ける根拠になっています。
暗号化・ハッシュ化・エンコーディングの3者比較
暗号化・ハッシュ化に加え、エンコーディングもセキュリティの文脈で混同されがちです。
| 項目 | 暗号化 | ハッシュ化 | エンコーディング |
|---|---|---|---|
| 可逆性 | 可逆(鍵で復号) | 不可逆 | 可逆(鍵不要) |
| 鍵の要否 | 必須 | 不要 | 不要 |
| 出力長 | 入力サイズに依存 | 常に固定長 | 入力サイズに依存 |
| 目的 | データの機密性保護 | データの完全性検証 | データ形式の変換 |
| セキュリティ機能 | あり | あり | なし |
| 代表例 | AES-256, RSA | SHA-256, bcrypt | Base64, URLエンコード |
Base64は誰でもデコードできる単なる形式変換のため、パスワードや秘密鍵の保護には一切使えません。「Base64で暗号化」という表現は技術的な誤用です。
暗号化の仕組みと主要アルゴリズム
暗号化は、平文を鍵とアルゴリズムで解読不能な形に変換し、正当な鍵を持つ者だけが復号できるようにする技術です。
共通鍵暗号方式(対称暗号)
送受信者が同一の鍵を共有する方式です。高速に処理できるため、大量データの暗号化に適しています。
| アルゴリズム | 鍵長 | ブロックサイズ | 備考 |
|---|---|---|---|
| AES | 128 / 192 / 256 bit | 128 bit | 現行の標準規格。NISTが2001年にFIPS 197として制定 |
| ChaCha20 | 256 bit | ストリーム暗号 | TLS 1.3で採用。モバイル環境で高速 |
| 3DES | 168 bit(実効112 bit) | 64 bit | NISTが2023年末に正式廃止。新規利用は不可 |
AES-256は2026年時点で解読報告がなく、米国政府の機密情報保護にも採用されています。
公開鍵暗号方式(非対称暗号)
暗号化に公開鍵、復号に秘密鍵というペアを使う方式です。鍵配送の問題を解消できますが、共通鍵暗号よりも処理速度は劣ります。
| アルゴリズム | 安全性の根拠 | 代表的な利用場面 |
|---|---|---|
| RSA | 素因数分解の計算困難性 | TLS証明書、電子署名 |
| ECDSA | 楕円曲線離散対数問題 | Bitcoinのトランザクション署名、TLS |
| Ed25519 | 楕円曲線離散対数問題 | SSH鍵、Gitコミット署名 |
ハイブリッド暗号 — TLS通信の実態
HTTPS通信では公開鍵暗号と共通鍵暗号を組み合わせるハイブリッド方式が標準です。TLSハンドシェイクで公開鍵暗号により共通鍵を安全に交換し、以降のデータ通信は高速な共通鍵暗号(AES-256-GCMなど)で処理します。公開鍵暗号の安全な鍵交換と、共通鍵暗号の処理速度を両立した構成です。
ハッシュ関数の動作原理
ハッシュ化は、任意の長さの入力をハッシュ関数に通し、固定長のハッシュ値(ダイジェスト)に変換する処理です。同じ入力からは必ず同一のハッシュ値が生成されますが、ハッシュ値から入力を逆算する手段はありません。
安全なハッシュ関数が満たすべき3条件
| 性質 | 定義 | 破られた場合のリスク |
|---|---|---|
| 原像耐性 | ハッシュ値から元の入力を推定できない | パスワードや機密データの復元 |
| 第二原像耐性 | ある入力と同じハッシュ値を生成する別の入力を見つけられない | 正規ファイルの差し替え攻撃 |
| 衝突耐性 | 同一ハッシュ値を出力する異なる入力ペアを見つけられない | 証明書偽造、署名の不正利用 |
代表的なハッシュアルゴリズム
| アルゴリズム | 出力長 | 安全性(2026年時点) | 主な用途 |
|---|---|---|---|
| MD5 | 128 bit | 衝突耐性が破られ非推奨 | レガシーチェックサム(新規利用は避ける) |
| SHA-1 | 160 bit | 2017年に衝突が実証され非推奨 | Git内部利用(SHA-256へ移行中) |
| SHA-256 | 256 bit | 安全 | ファイル検証、TLS証明書、ブロックチェーン |
| SHA-3(Keccak) | 224〜512 bit | 安全。SHA-2と異なるスポンジ構造 | SHA-2の代替、新規システム |
| BLAKE3 | 256 bit | 安全。SHA-256より高速 | 大規模ファイル検証、新規アプリケーション |
SHA-1の衝突は2017年にGoogleとCWI Amsterdamの共同研究「SHAttered」で実証されました(出典: SHAttered)。
雪崩効果をコードで確認する
入力のわずかな変化がハッシュ値を大きく変える性質は雪崩効果(avalanche effect)と呼ばれ、改ざん検知の基盤になっています。
Bashの場合:
echo -n "hello" | sha256sum
# 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
echo -n "Hello" | sha256sum
# 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969
先頭1文字を大文字にしただけで、256ビットの出力が完全に異なる値になります。
Pythonの場合:
import hashlib
for text in ["hello", "Hello"]:
digest = hashlib.sha256(text.encode()).hexdigest()
print(f"{text:>5} → {digest}")
# hello → 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
# Hello → 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969
ハッシュ化を「元に戻せない」数学的な理由
ハッシュ化が不可逆である根本的な原因は情報量の消失です。
SHA-256は、1バイトの入力も1テラバイトの入力も同じ256ビット(32バイト)に圧縮します。入力の組み合わせは事実上無限ですが、出力は2^256通り(約1.16 × 10^77通り)に限定されます。鳩の巣原理により、異なる入力が同じハッシュ値になるケース(衝突)は理論上必ず存在するため、あるハッシュ値から元の入力を一意に特定する方法は原理的にありません。
ただし「元に戻せない」は「推測もできない」という意味ではありません。password123 のような短く推測しやすい入力に対しては、辞書攻撃やレインボーテーブル(事前計算済みの平文↔ハッシュ対応表)で高速に突き止められます。この弱点への対策がソルトとストレッチングです。
パスワードを「暗号化」ではなく「ハッシュ化」で保存する理由
パスワードを暗号化で保存すると、暗号鍵が漏洩した時点で全ユーザーのパスワードが一括復号されるリスクを抱えます。2012年のLinkedIn情報流出では、SHA-1によるハッシュ化は行われていたもののソルトが付与されていなかったため、約650万件のパスワードハッシュが公開され多数が短時間で解読されました(出典: LinkedIn公式ブログ)。
ハッシュ化であれば暗号鍵そのものが存在しないため、「鍵漏洩で全件復元」というリスク自体が発生しません。正規のログイン処理では、ユーザーが入力したパスワードをハッシュ化し、データベースの保存済みハッシュ値と一致するかを比較するだけで認証が完了します。
「パスワードを忘れた場合」に再設定しかできない理由
多くのWebサービスでパスワードを忘れた際に「現在のパスワードを表示する」機能がないのは、サーバー側にパスワードの平文が存在しないためです。保存されているのはハッシュ値のみであり、システム管理者ですらユーザーの元のパスワードを知ることができません。セキュリティが適切に設計されたサービスでは、新しいパスワードを設定し直すリセットが唯一の回復手段です。
ソルト・ペッパー・ストレッチングの三重防御
単純なハッシュ化だけでは不十分です。攻撃への実用的な耐性を確保するため、3つの防御策を組み合わせます。
ソルト(salt) — ハッシュ化前にパスワードへ付加するランダム文字列です。ユーザーごとに異なるソルトを生成し、ハッシュ値と一緒にデータベースに保存します。同じパスワード password123 でもソルトが a8f2k9... と p3x7m1... では異なるハッシュ値になるため、レインボーテーブルによる一括照合を無効化できます。
ペッパー(pepper) — アプリケーション側で管理する秘密の固定値です。データベースとは別の場所(環境変数やHSM)に保管し、ソルトに加えてもう一段の防御層を設けます。データベースが流出してもペッパーがなければハッシュの再計算ができないため、追加のセキュリティマージンとして機能します。
ストレッチング(key stretching) — ハッシュ計算を数千〜数十万回繰り返し、1回の検証にかかる時間を意図的に引き伸ばす手法です。正規ユーザーの操作には数百ミリ秒の遅延しか生じませんが、毎秒数十億回の試行を狙う攻撃者には膨大な計算コストを強います。
パスワード専用ハッシュアルゴリズムの選定基準
SHA-256のような汎用ハッシュ関数はパスワード保存に向きません。高速処理を目的に設計されているため、GPUを使えば毎秒数十億回のハッシュ計算が可能です。パスワード専用に設計されたアルゴリズムを選択する必要があります。
| アルゴリズム | ソルト | ストレッチング | メモリハード | GPU耐性 | 推奨度 |
|---|---|---|---|---|---|
| Argon2id | 内蔵 | 反復回数指定 | 高(設定可能) | 非常に高い | 最推奨 |
| bcrypt | 内蔵 | コストファクター | 低い | 中程度 | 推奨 |
| scrypt | 内蔵 | 反復回数指定 | 高(設定可能) | 高い | 推奨 |
| PBKDF2 | 外部付与 | 反復回数指定 | 低い | 低い | レガシー向け |
Argon2idは2015年のPassword Hashing Competition(PHC)優勝アルゴリズムで、OWASPのPassword Storage Cheat Sheetでも第一推奨とされています(出典: OWASP)。OWASPが示す最小パラメータは、メモリ19 MiB・反復回数2・並列度1です。bcryptを使用する場合はコストファクター10以上が推奨されます。
Pythonでのbcrypt実装例
import bcrypt
# パスワードのハッシュ化(ソルト自動生成・ストレッチング内蔵)
password = b"my_secure_password"
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
print(hashed)
# b'$2b$12$LJ3m4ys3Kl...(以下省略)'
# ログイン時の照合
if bcrypt.checkpw(password, hashed):
print("認証成功")
出力に含まれる $2b$12$ はbcryptのバージョン(2b)とコストファクター(12)を示しています。ソルトもハッシュ値に埋め込まれるため、別カラムでの管理は不要です。
HMAC — ハッシュ関数に秘密鍵を組み合わせるメッセージ認証
HMAC(Hash-based Message Authentication Code)は、ハッシュ関数と秘密鍵を組み合わせて「メッセージが改ざんされていないこと」と「送信者が秘密鍵の正当な所有者であること」を同時に検証する仕組みです。
通常のハッシュ化ではデータの完全性(改ざんの有無)しか確認できません。HMACでは秘密鍵を知る者だけが正しい認証コードを生成できるため、送信者の真正性まで保証されます。
APIのWebhook署名検証の実装例
Stripe・GitHub・AWSなどの主要サービスは、Webhook通知にHMAC-SHA256の署名を付与しています。受信側での検証コードは以下のようになります。
import hmac
import hashlib
secret = b"webhook_secret_key"
payload = b'{"event":"payment.completed","amount":1000}'
# 送信側が生成した署名
signature = hmac.new(secret, payload, hashlib.sha256).hexdigest()
# 受信側での検証
expected = hmac.new(secret, payload, hashlib.sha256).hexdigest()
if hmac.compare_digest(signature, expected):
print("署名が有効")
hmac.compare_digest() はタイミング攻撃を防ぐための定時間比較関数です。通常の == 演算子は一致しない箇所が見つかった時点で処理を打ち切るため、応答時間の差から秘密情報を推測されるリスクがあります。
デジタル署名 — 暗号化とハッシュ化を組み合わせる代表技術
デジタル署名は、ハッシュ化と公開鍵暗号を組み合わせた技術です。「メッセージが改ざんされていないこと」と「署名者が本人であること」を第三者が検証できます。
- 送信者がメッセージのSHA-256ハッシュ値を計算する
- そのハッシュ値を送信者の秘密鍵で暗号化する(= 署名の生成)
- 受信者は送信者の公開鍵で署名を復号し、ハッシュ値を取り出す
- 受信者が自分でメッセージのハッシュ値を計算し、復号したハッシュ値と照合する
- 一致すれば、改ざんがなく秘密鍵の所有者が署名したことが証明される
HTTPS通信のサーバー証明書検証、ソフトウェアのコード署名、電子契約など、インターネットの信頼基盤の多くがこの仕組みに支えられています。
暗号化とハッシュ化の適用場面
暗号化が適するケース
| 場面 | 具体例 | 代表アルゴリズム |
|---|---|---|
| 通信経路の保護 | HTTPS / VPN | AES-256-GCM, ChaCha20-Poly1305 |
| ストレージの保護 | ディスク暗号化、DB列暗号化 | AES-256-XTS |
| メッセージの秘匿 | E2Eメッセージングアプリ | Signal Protocol |
| ファイル暗号化 | GPG、暗号化ZIP | AES-256 |
ハッシュ化が適するケース
| 場面 | 具体例 | 代表アルゴリズム |
|---|---|---|
| パスワード保存 | Webアプリのユーザー認証 | Argon2id, bcrypt |
| ファイル改ざん検知 | ソフトウェア配布時のチェックサム | SHA-256 |
| ブロックチェーン | トランザクション検証 | SHA-256(Bitcoin), Keccak-256(Ethereum) |
| バージョン管理 | Gitのコミットオブジェクト | SHA-1(SHA-256移行中) |
| メッセージ認証 | APIのWebhook署名検証 | HMAC-SHA256 |
選択の判断基準
| 問い | 暗号化を選ぶ | ハッシュ化を選ぶ |
|---|---|---|
| 元データの復元が必要か | はい | いいえ |
| 保護の目的は機密性か | はい | いいえ(完全性の検証) |
| 鍵管理のコストを許容できるか | はい | 鍵管理は不要 |
判断例:
- クレジットカード番号 → 暗号化(後続の決済処理で平文が必要)
- ログインパスワード → ハッシュ化(平文は不要。入力のハッシュと保存済みハッシュを比較するだけ)
- HTTPS通信のペイロード → 暗号化(受信側で復号して内容を読む必要がある)
- ダウンロードファイルの正当性確認 → ハッシュ化(配布元が公開するハッシュ値と照合するだけ)
まとめ
暗号化は鍵を用いてデータを可逆変換し、機密性を守る技術です。ハッシュ化は入力を固定長のダイジェストに不可逆変換し、完全性の検証に使います。エンコーディングはデータ形式の変換にすぎず、セキュリティ機能はありません。
パスワードの保存にはArgon2idまたはbcryptを採用し、ソルトとストレッチングを必ず組み込みます。ファイル検証や署名にはSHA-256以上の強度を持つハッシュ関数を選択してください。MD5とSHA-1は衝突耐性が破られているため、新規の用途では使用を避けるべきです。
APIの署名検証にはHMAC-SHA256を使い、定時間比較関数でタイミング攻撃を防ぐことも重要なポイントです。暗号化とハッシュ化の性質を正しく理解し、用途に合った技術を選ぶことが堅牢なセキュリティ設計の出発点になります。
