実務者向けソルトとパスワードハッシュの完全ガイド:推奨アルゴリズム選択と安全な運用テクニック

はじめに — 「ハッシュ化」と「ソルト(salt)」の関係

ユーザーパスワードや機密データの保護において「ハッシュ化」と「ソルト」は切っても切れない概念です。単なるハッシュだけでは実運用上の攻撃(レインボーテーブルやオフライン総当たり)に弱いため、「ソルトを付ける」という設計上の工夫が必須になります。本稿では、ソルトとは何か、何のために必要か、正しい作り方・運用、よくある誤解と対策、実際に使うべきアルゴリズムや実装上の注意点まで、実務者向けに詳しく解説します。

ハッシュ化とは何か(短く整理)

ハッシュ化は任意長の入力データを固定長の値(ハッシュ値)に変換する処理です。一般的な特徴としては一方向性(ハッシュ値から元の入力を容易に復元できない)と衝突耐性(異なる入力が同じハッシュ値になる確率が低い)があります。代表的なハッシュ関数には SHA-256、SHA-3 などがあります。ただし、パスワード保護に単純な高速ハッシュ(例えば SHA-256)を使うのは推奨されません。攻撃者がオフラインで大量のハッシュを高速に計算して総当たり(ブルートフォース)を行えるためです。

ソルト(salt)とは何か

ソルトとは「各パスワードハッシュに追加するランダムな付加データ」のことです。ハッシュの入力としてパスワードにソルトを連結(あるいは別方法で組み合わせ)してハッシュ化します。ソルトは一般に公開(非機密)でも問題ありません。重要なのは「ランダムで一意であること」と「適切に保存されること」です。

ソルトを使う目的(なぜ必要か)

  • レインボーテーブル攻撃の無効化:ソルトにより同じパスワードでも異なるハッシュ値になるため、事前に計算した固定のレインボーテーブルが使えなくなります。
  • 複数ユーザー間の同一パスワード判別防止:ユーザーAとBが同じパスワードでも、異なるソルトならハッシュは一致しません。
  • オフライン辞書攻撃のコスト増加:ソルトがあれば攻撃者は各ソルトごとに総当たりを行う必要があり、計算コストが上がります(ただしソルトだけでは十分ではない)。

ソルトだけでは不十分な理由 — ストレッチングが必要

ソルトはレインボーテーブル等を無効化しますが、攻撃者がユーザーのハッシュとソルトを入手してしまえば、オフライン総当たり(辞書攻撃)は依然として可能です。したがって「計算コストを意図的に上げる」ことが重要で、これを実現するのが PBKDF2、bcrypt、scrypt、Argon2 といったパスワード専用のハッシュ関数(キー導出関数)です。これらは反応時間(計算時間)やメモリ使用量を増やすことで攻撃コストを高めます(ストレッチング・メモリハードネス)。

ソルトの生成と保存のベストプラクティス

  • 一意かつランダムに生成:各パスワードに対して新規にランダムなソルトを生成する(ユーザー間で再利用しない)。
  • 長さ:少なくとも 16 バイト(128 ビット)以上が推奨。ただし多すぎても実害はない。ライブラリ既定の値を尊重するのが現実的。bcryptは内部で 16 バイトを使う等、アルゴリズム依存。
  • 安全な乱数源を使う:/dev/urandom、CryptGenRandom、Windows CNG、言語標準の CSPRNG(例:Python の secrets、Node の crypto.randomBytes)などを使う。
  • ソルトは秘密にする必要はない:データベースの同じレコードに平文で保存して問題ない。むしろソルトを保持しないと検証ができない。
  • 格納方法:ソルトをハッシュと同じテーブルに別カラムで持つか、ハッシュ内部にエンコードして保存する(例:bcrypt や Argon2 の出力はソルト・パラメータ・ハッシュを含む文字列を返す)。
  • 既存ライブラリの機能を使う:多くのパスワードハッシュライブラリはソルト生成を内蔵している。手動でソルトを管理する必要は原則ないし、危険な場合がある。

ソルトの長さ・形式・エンコーディング

ソルトはバイナリデータなので、データベースへはバイナリ型(BLOB)で保存するか、Base64 や hex でエンコードしてテキスト型に保存します。ソルトの長さはアルゴリズムにも依存しますが、16〜32バイト(128〜256ビット)を目安にすれば十分です。bcrypt のようなアルゴリズムは自動でソルトを生成し、出力文字列に含めるため、別途保存する必要はありません。

ソルトと「ペッパー(pepper)」の違い

ペッパーはソルトとは別に使われる「追加の秘密データ」です。ソルトは公開してもよい一方、ペッパーは秘密であることが前提です(アプリケーションの設定や HSM、別サーバに保管)。目的は、もしデータベースが漏えいしてもペッパーが守りを強化することですが、運用が難しく、鍵管理(バックアップ・ローテーション・アクセス制御)が必要です。注意点として、ペッパーをアプリケーション内部にハードコードするのはリスクが高いです。

ソルトと暗号化の初期化ベクトル(IV)や HMAC との違い

  • IV(初期化ベクトル):対称暗号(例:AES-CBC、AES-GCM)で用いる無作為値で、暗号化の再利用性を防ぐ目的。ソルトと似ていますが、目的が「暗号文のランダム化」かつアルゴリズム固有です。
  • HMAC のキー:HMAC はシークレットキーに基づくメッセージ認証であり、ソルトとは概念が異なります。HMAC のキーは秘密である必要があります。

よくある実装ミス(アンチパターン)

  • ソルトを固定値にする(アプリケーション全体で同一のソルトを使う) — レインボーテーブル対策にならない。
  • ソルトをユーザー名やメールアドレスなど予測可能な値にする — ランダム性がなく攻撃を容易にする。
  • 高速なハッシュ(SHA-256 等)+ソルトのみで済ます — オフライン攻撃に脆弱。PBKDF2/Bcrypt/Argon2 を使うべき。
  • ライブラリの自動ソルト生成を無視して自前実装する — 微妙なバグを生む可能性が高い。既存の信頼できる実装を利用する。例えば PHP の password_hash は自動で安全にソルトを作る。
  • ペッパーを平文でソース管理に入れてしまう — 漏洩時に意味をなさなくなる。

推奨されるアルゴリズムと実装例

パスワード保存には以下を推奨します(優先順や使用理由を併記)。

  • Argon2(特に Argon2id):Password Hashing Competition(PHC)優勝者で、メモリハード性と並列性の制御が可能。現代的で推奨される選択肢。
  • scrypt:メモリハードで良好。ただしパラメータ設計に注意。
  • bcrypt:長年の実績があり安全だがメモリハード性が弱い。レガシー互換や簡便性の観点で有用。
  • PBKDF2:ハードウェアでの高速総当たりに対する耐性は他に比べ劣るが、標準化されており FIPS 要件で使用される場合がある。反復回数を十分に増やす必要あり。

実装面では、言語標準や信頼できるライブラリを使い、ライブラリがソルトを自動で生成するならそれに任せましょう。例:PHP の password_hash/password_verify(bcrypt/Argon2)、Python の passlib、Node.js の argon2/bcrypt パッケージなど。

DB スキーマの例とハッシュアルゴリズムの保存

推奨される設計例:

  • users テーブルにカラム:password_hash(TEXT/BLOB)、salt(BLOB、不要な場合あり)、hash_algorithm(文字列)、cost_param(数値)
  • 理由:ハッシュアルゴリズムやコストパラメータを保存しておくと将来のアルゴリズム移行(リハッシュ)が容易。

ただし bcrypt/Argon2 のように出力文字列がアルゴリズム・パラメータ・ソルト・ハッシュを含む形式を返すものは、password_hash の出力をそのまま password_hash カラムに保存すれば salt を別カラムに分ける必要はありません。

ハッシュアルゴリズム移行(リハッシュ)の運用パターン

  • ゆっくり移行(on-login rehash):ユーザーがログインしたタイミングで現在のハッシュを検証し、新しいアルゴリズム/コストで再ハッシュして保存する。
  • 全件バッチ更新(注意:プレーンテキストが必要):これは不可能/危険な場合が多い。正攻法は on-login rehash。
  • アルゴリズム情報を保存:どのレコードが古いアルゴリズムか識別できるように、ハッシュ文字列内や別カラムにメタ情報を保持する。

運用上の注意点

  • テスト環境でも本番と同等のアルゴリズムとコストを検証して性能影響を確認する(レスポンスタイム、スループット)。
  • コスト設定は定期的に見直す(ハードウェアの進化に合わせて上げる)。
  • ペッパーを使う場合は鍵管理方針を明確にする(キーローテーション、バックアップ、アクセス制限)。
  • 監査ログやエラーログにパスワードやソルトが漏れることのないようにする。

まとめ

ソルトはパスワードハッシュを強固にするための重要な要素ですが、単体では十分ではありません。ソルトは「各パスワードごとの一意なランダム値」として必ず利用し、かつパスワードハッシュには計算コストやメモリコストを調整できる専用関数(Argon2、scrypt、bcrypt、PBKDF2 など)を使用することが現代的なベストプラクティスです。さらに、運用面でのアルゴリズム情報の保存、コストパラメータの更新、適切な鍵管理(ペッパー)を組み合わせることで、実用に耐える安全なパスワード保存設計になります。ライブラリの既定に従い、自前実装は避けてください。

参考文献