ペッパー(pepper)とは何か — ソルトとの違いとKMS/HSM活用で実現する安全なパスワードハッシュ設計ガイド

はじめに — 「ペッパー(pepper)」とは何か

パスワードのハッシュ化における「ペッパー(pepper)」は、パスワードに付加される秘密情報(サーバー側で管理される鍵や固定値)を指します。一般にユーザーごとにデータベースに保存される「ソルト(salt)」と混同されがちですが、役割・保管場所・脅威モデルが異なります。ペッパーはデータベース外に保管され、外部からのデータベース流出のみでは攻撃者が容易にパスワードを復元できないようにするための追加的な防御手段です。

ソルトとペッパーの違い

  • ソルト(salt):各ユーザーごとにランダムに生成され、同じパスワードでも異なるハッシュになるようにするための値。通常、ハッシュ値とともにユーザーレコードに平文で保存される。主にレインボーテーブル攻撃を防ぎ、同一パスワードの判別を困難にする。
  • ペッパー(pepper):システム全体(または一部ユーザー用)で共通に使う秘密値や鍵。データベースとは別の場所(アプリ設定、KMS、HSMなど)で厳重に保管される。データベースが漏えいしてもペッパーが安全なら、オフラインの総当たり攻撃耐性が大きく向上する。

攻撃シナリオ別の効果

  • データベースのみが流出:ペッパーが別保管なら攻撃者はハッシュを直接総当たりで照合できず、守備効果が高い。
  • アプリサーバーや設定も流出(=ペッパーも漏えい):ペッパーは無効化され、効果は消える。したがって、ペッパーはデータベースとは物理的・論理的に分離して保護することが肝要。
  • オンライン攻撃(ログイン試行):ペッパーは直接的な防御にならない。レート制限や多要素認証(MFA)が重要。

ペッパーの実装方法(安全なパターン)

実装時のポイントは「安全な鍵管理」と「適切なハッシュ関数の選択」です。以下は主な実装パターンです。

  • KDFの「秘密」パラメータとして渡す(推奨)
    Argon2(libsodiumのcrypto_pwhash等)は「secret」や「key」として外部秘密を受け取れる場合があります。これによりKDF内部で秘密が扱われ、より堅牢になります。
  • HMACを用いて先に鍵付き要約を作成し、その結果をKDFに入力する
    例:derived = bcrypt(HMAC-SHA256(pepper, password) + salt) のように、pepperをHMACの鍵として用いる手法はよく用いられます。pepperを単純に文字列連結するよりも鍵付きハッシュ(HMAC)として扱う方が安全性が高いです。
  • グローバルpepper vs per-user pepper
    グローバルpepperは運用が簡単ですが、一つの鍵漏洩で全ユーザーが危険にさらされます。可能なら、ユーザー群ごとに鍵を分割する、あるいは鍵ローテーションを容易にする設計(pepperのバージョン管理)を検討してください。

具体的な実装例(概念)

下記は概念的なフローです。実装時はライブラリの仕様や推奨パターンに従ってください。

  • 登録時:
    • 1) ランダムなソルトを生成しDBに保存。
    • 2) pepperはKMS/HSMから取得(平文でDBには保存しない)。
    • 3) HMAC-SHA256(pepper, password) を計算し、その出力をArgon2idでハッシュ(saltを引数に)してDBに保存。
  • 認証時:
    • 1) ユーザー入力のパスワードを受け取り、KMSからpepperを取得。
    • 2) 同様にHMAC→Argon2idを計算し、DB上のハッシュと比較。

鍵管理(KMS/HSM)の重要性

ペッパーを安全に運用するためには、単なる環境変数やソースコード内のハードコーディングは避けるべきです。推奨される保管場所は次のとおりです。

  • AWS KMS / AWS Secrets Manager
  • Google Cloud KMS / Secret Manager
  • Azure Key Vault
  • オンプレミスのHSM(Hardware Security Module)

これらのサービスではアクセス制御・監査ログ・自動ローテーションなどの機能が提供され、漏洩リスクと運用負荷を軽減できます。アプリからはKMS経由で鍵を取得するか、HSMに鍵を保持したまま署名/復号といった操作を委任するパターンが望ましいです。

ペッパーのローテーションと運用

  • 直接ハードコードされたpepperの変更は、既存ユーザー全員のパスワード再ハッシュが必要になるため運用コストが高い。
  • 対策としては「pepperバージョン」を導入し、DBにどのpepperでハッシュ化したかを示すバージョン情報を持たせる。認証時に古いpepperでチェックし成功したら新しいpepperで再ハッシュして更新する方式が一般的。
  • ただし、初回ログインまで古いpepperのまま残るため、即時の完全移行が必要な場面では全レコードの一括再ハッシュ処理が必要です(ただし再ハッシュにはユーザーの平文パスワードが必要なため、通常は実行不可)。

限界と注意点

  • ペッパーは万能ではない。アプリケーション層や運用環境が侵害された場合、その効果は失われる。
  • オンライン攻撃(ブルートフォースでログイン試行)には無効。レート制限、多要素認証、異常検知が必要。
  • 法的・規制要件(GDPR等)は技術的対策だけで完結しない。漏洩時の通知義務やデータ保護責任の検討が必要。
  • 設計時にはKDF(Argon2, bcrypt, scrypt, PBKDF2等)を使用し、十分な計算コスト・メモリコストを設定することが重要。

実務的なベストプラクティスまとめ

  • パスワード保護は「複数の層」で行う:ソルト + 強力なKDF + ペッパー + 鍵管理。
  • ペッパーはデータベースとは別で管理し、アクセスは最小限に限定、KMS/HSMの利用を検討する。
  • ペッパーを利用する場合は、HMACやKDFの「秘密」パラメータとして安全に組み込む(単純連結は避ける)。
  • ペッパーのローテーション方針を事前に設計し、バージョン管理や段階的再ハッシュ戦略を用意する。
  • パスワード保護は単体では不十分。MFA、ログ監視、レートリミットなど運用的対策を組み合わせる。

参考実装例(擬似コード)

例:HMACを先に使い、その出力をArgon2に渡すパターン(擬似コード)

pepper = KMS.get_secret("pepper-v1")   // KMSから取得
salt = random_bytes(16)                    // DBに保存
hmac = HMAC_SHA256(key=pepper, data=password)
hash = Argon2id(hash_input=hmac, salt=salt, time=3, mem=65536)
store_in_db(user, salt, hash, pepper_version=1)

まとめ

ペッパーは、ソルトや強力なKDFと組み合わせることでパスワード漏洩時の被害を減らす有効な手段です。ただし鍵管理が不十分であれば効果は失われるため、KMS/HSMの活用、ローテーション方針、運用体制の整備が不可欠です。また、ペッパーはあくまで一要素であり、MFAや監視・制限といった他の防御層と組み合わせることが重要です。

参考文献