MENU

パスワードのハッシュ化とソルトとは?bcrypt・Argon2・scryptで安全なパスワード保存を実現する実践ガイド

パスワード漏洩インシデントのニュースが絶えない昨今、「ハッシュ化して保存しているから大丈夫」と思っていませんか。残念ながら、MD5やSHA-1で単純にハッシュ化するだけでは、攻撃者に対して十分な保護になりません。
2012年のLinkedIn流出事件では約600万件のSHA-1ハッシュが流出し、そのほとんどが数時間で解読されました。サービスを提供するエンジニアにとって、パスワードの安全な保存方法は最低限の知識です。

この記事では、パスワードのハッシュ化・ソルト・最新の推奨アルゴリズム(bcrypt・Argon2・scrypt)の仕組みと実装方法を、現場で使えるレベルで解説します。

パスワードのハッシュ化とソルトとは?bcrypt・Argon2・scryptで安全なパスワード保存を実現する実践ガイド - 解説

目次

パスワードのハッシュ化とは?なぜ必要か

パスワードをデータベースに保存する際、平文(そのままの文字列)で保存することは絶対に避けるべきです。データベースが漏洩した場合、全ユーザーのパスワードが即座に攻撃者の手に渡るからです。

ハッシュ化とは、任意の入力文字列を固定長のハッシュ値に変換する一方向の関数処理です。ハッシュ値から元のパスワードを復元することは(理論上)できないため、データベースが漏洩してもすぐには悪用されません。

ただし「ハッシュ化すれば安全」という認識は半分しか正しくありません。問題は、使うアルゴリズムと実装方法にあります。

# 平文保存(絶対NG) password = "my_password123" store(user_id, password) # そのまま保存 # 単純ハッシュ(危険) import hashlib hashed = hashlib.md5("my_password123".encode()).hexdigest() store(user_id, hashed) # "49bbd0a2a88..." を保存 → 解読可能

MD5・SHA-1が危険な理由|レインボーテーブル攻撃とブルートフォース

MD5やSHA-1は汎用ハッシュ関数であり、「速く計算できる」ことを目的に設計されています。これがパスワード保存には致命的な欠点です。

1. レインボーテーブル攻撃

よく使われるパスワードのハッシュ値を大量に事前計算し、テーブルとして保存しておく攻撃手法です。流出したハッシュをテーブルと照合するだけで、元のパスワードが特定できます。

・「password」のMD5: 5f4dcc3b5aa765d61d8327deb882cf99
・「123456」のMD5: e10adc3949ba59abbe56e057f20f883e
・「monkey」のMD5: d0763edaa9d9bd2a9516280e9044d885

これらは公開されているレインボーテーブルに既に含まれており、解読に数秒もかかりません。

2. ブルートフォース(総当たり)の高速化

MD5のハッシュ計算は非常に高速です。現代のGPUを使えば、1秒間に数十億回のMD5計算が可能です。8文字以下の英数字パスワードは、数時間で全パターンを試されてしまいます。この問題はソルトだけでは解消できず、専用のアルゴリズムが必要になります。

ソルトとは?レインボーテーブルを無効化する仕組み

ソルト(Salt)とは、ハッシュ化の前にパスワードに付加するランダムな文字列です。ユーザーごとに異なるソルトを生成し、「ソルト+パスワード」をまとめてハッシュ化します。

・役割1: 同じパスワードでも異なるハッシュ値が生成される → レインボーテーブル無効化
・役割2: 攻撃者は各ユーザーのハッシュに対して個別にブルートフォースを実行する必要がある → 攻撃コスト増大
・役割3: 複数サービスで同じパスワードを使っていても、ソルトが異なれば異なるハッシュになる

# ソルトありのハッシュ化(概念。実際はArgon2等を使うこと) import os, hashlib salt = os.urandom(16) # ランダムな16バイトのソルト password = "my_password123" hashed = hashlib.sha256(salt + password.encode()).hexdigest() # ユーザーごとに異なるソルトをDBに保存 store(user_id, salt.hex(), hashed) # ただし SHA-256 + 手動ソルトは推奨しない # → 後述の専用アルゴリズム(Argon2等)を使うこと

ただし、ソルトがあってもMD5やSHA-256では速度の問題が解消されません。パスワード専用のアルゴリズムが必要です。

推奨アルゴリズムの選び方|bcrypt・scrypt・Argon2

パスワード保存に適したアルゴリズムは「意図的に計算コストが高い(遅い)」設計になっています。攻撃者のブルートフォースを現実的でない時間にするためです。

1. bcrypt(1999年~)

長年使われてきた実績のあるアルゴリズムです。コストファクター(work factor)でハッシュ計算の難しさを調整できます。コストファクターを1増やすと計算時間が約2倍になります。

・メリット: 枯れた技術・幅広い言語でサポート済み・CPU時間に強い
・デメリット: メモリ使用量が少ないためGPU並列攻撃にやや弱い・72バイトの長さ制限あり
・推奨コストファクター: 12(実行時間約250ms相当)

2. scrypt(2009年~)

bcryptの弱点であるGPU並列攻撃を意識して設計されました。メモリも大量消費するため、専用ハードウェアによる攻撃にも強いです。

・メリット: GPU・ASICへの耐性が高い
・デメリット: パラメータ設定が複雑・サポートライブラリが限られる
・用途: 暗号通貨(Litecoin)にも採用。新規プロジェクトではArgon2を推奨

3. Argon2(2015年~・現在の推奨)

Password Hashing Competition(2015年)の優勝アルゴリズムです。現時点でパスワードハッシュ化の最新推奨標準です。

・Argon2d: GPU攻撃への耐性最強(データ依存メモリアクセス) → 暗号通貨向け
・Argon2i: サイドチャネル攻撃への耐性 → パスワード保存向け
・Argon2id: Argon2dとArgon2iのハイブリッド → パスワード保存の最推奨

OWASPとNISTともにArgon2idを第一推奨としています(2023年以降)。

具体的な実装手順

1. PHP(password_hashで組み込みbcrypt/Argon2)

PHPはバージョン7.2以降でArgon2をネイティブサポートしています。

$password = $_POST['password']; $options = [ 'memory_cost' => 65536, // 64MB 'time_cost' => 4, // 4回の反復 'threads' => 1, ]; // ハッシュ化(ソルトは自動生成される) $hash = password_hash($password, PASSWORD_ARGON2ID, $options); // → $hash をDBに保存 // 検証(ソルト・アルゴリズムは$hashに含まれる) if (password_verify($_POST['password'], $hash)) { // 認証成功 } // リハッシュが必要か確認(アルゴリズム移行時) if (password_needs_rehash($hash, PASSWORD_ARGON2ID, $options)) { $newHash = password_hash($password, PASSWORD_ARGON2ID, $options); // DBを更新する }

2. Python(argon2-cffiライブラリ)

# pip install argon2-cffi from argon2 import PasswordHasher from argon2.exceptions import VerifyMismatchError ph = PasswordHasher( time_cost=4, # 反復回数 memory_cost=65536, # 64MB parallelism=1, hash_len=32, salt_len=16 ) # ハッシュ化(ソルトは自動生成) password = "my_password123" hash_value = ph.hash(password) # → hash_value をDBに保存 # 検証 try: ph.verify(hash_value, "my_password123") print("認証成功") except VerifyMismatchError: print("認証失敗") # リハッシュが必要か確認 if ph.check_needs_rehash(hash_value): new_hash = ph.hash(password) # DBを更新する

3. Node.js(argon2ライブラリ)

// npm install argon2 const argon2 = require('argon2'); // ハッシュ化 const password = 'my_password123'; const hash = await argon2.hash(password, { type: argon2.argon2id, memoryCost: 65536, // 64MB timeCost: 4, parallelism: 1, }); // → hash をDBに保存 // 検証 try { if (await argon2.verify(hash, password)) { console.log('認証成功'); } } catch (err) { console.error('検証エラー:', err); }

中小企業でも今日からできること

既存のシステムでMD5やSHA-1を使っている場合、移行には計画が必要ですが、以下の手順で段階的に対応できます。

・Step1 現状確認: 使用しているハッシュアルゴリズムをコードまたはDBで確認する
・Step2 新規登録から移行: 新しく登録されるパスワードはArgon2idでハッシュ化する
・Step3 ログイン時に移行: 旧ハッシュで認証成功したユーザーのパスワードを、新アルゴリズムでリハッシュしてDBを上書きする
・Step4 完全移行の確認: 旧ハッシュのエントリがゼロになったら移行完了

WordPress・Laravel・Railsなど主要フレームワークはbcryptまたはArgon2を標準でサポートしており、設定変更のみで対応可能です。SaaSのIDaaS(Auth0・Amazon Cognito等)を使っている場合は、ハッシュ化はプロバイダー側が担うため、まず利用中のサービスの仕様を確認してみてください。

よくある誤解と注意点

・誤解1「SHA-256は安全なはずでは?」: SHA-256は汎用ハッシュ関数であり速度優先の設計です。パスワード保存には不適切です。「速さ」と「遅さ」の役割が逆になります。
・誤解2「ソルトをシークレットにすれば安全になる」: ソルトはランダム性が大事であり、秘密にする必要はありません。ソルトはハッシュと一緒にDBに保存します。秘密にするのは「ペッパー」と呼ばれる別の概念です。
・誤解3「コストファクターは高いほど良い」: 高すぎるとユーザーのログインが遅くなりUXを損ないます。サーバー負荷とのバランスを取り、認証に250ms~500ms程度かかるよう調整します。
・誤解4「フレームワークに任せれば問題ない」: フレームワークのデフォルト設定が古い場合があります。定期的にアルゴリズムとコストファクターを見直してください。

パスワードのハッシュ化とソルトとは?bcrypt・Argon2・scryptで安全なパスワード保存を実現する実践ガイド - まとめ

本記事のまとめ

アルゴリズム 推奨度 特徴
MD5 / SHA-1 ❌ 使用禁止 高速すぎる・レインボーテーブル対策なし
SHA-256 + 手動ソルト △ 非推奨 実装ミスが起きやすい・専用設計でない
bcrypt ○ 良好 実績あり・72バイト制限に注意
scrypt ○ 良好 GPU耐性高い・パラメータ複雑
Argon2id ◎ 最推奨 OWASP・NIST推奨・最新標準

パスワードの安全な保存は、システムの根幹を守るセキュリティの基礎です。暗号化の失敗(Cryptographic Failures)はOWASP Top 10の第2位に位置づけられており、パスワードの不適切な保存はその典型的な例です。まず現在のシステムで使っているアルゴリズムを確認し、MD5やSHA-1が残っていれば今すぐ移行計画を立てることをお勧めします。

安全なコーディング全般についてはセキュアコーディングの基本原則と実装ガイドも合わせてご覧ください。

PR

暗号技術入門 第3版(結城浩)

パスワードハッシュ・共通鍵暗号・公開鍵暗号・デジタル署名まで、暗号の仕組みを基礎から体系的に学べる定番書。ハッシュ関数の原理からソルトの必要性まで、本記事で触れた内容をより深く理解したいエンジニアに最適な一冊です。

関連記事をもっと読む

同じテーマの記事をまとめています。あわせて読みたい記事はこちらからご覧いただけます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次