bcryptとは何か徹底解説:ソルト・コスト・ハッシュ形式・運用ベストプラクティスとArgon2比較

導入:bcryptとは何か

bcryptはパスワードの安全な保存(パスワードハッシュ)を目的に設計されたハッシュ関数の一種です。1999年にNiels Provos と David Mazieres によって提案され、内部で改良された Blowfish 暗号の鍵スケジュール(EksBlowfish)を用いることで「意図的に遅く」・「計算コストを調整可能」にした点が特徴です。パスワード総当たり(ブルートフォース)や辞書攻撃に対する防御力を確保するため、ハッシュ化処理に計算コスト(ワークファクタ/コストパラメータ)を導入しています。

bcrypt の基本的な仕組み(概念レベル)

  • ソルト(salt):各パスワードに対してランダムな16バイト(128ビット)のソルトを生成します。ソルトは同じパスワードでも異なるハッシュ値を生成するため、レインボーテーブル攻撃を無効化します。
  • コスト(work factor):2のべき乗回の「高コストな鍵スケジュール(EksBlowfish)」を実行します。コストは整数で指定し、1増やすごとに処理時間が約2倍になります(例:cost=12 は 2^12 回の反復)。
  • ハッシュ出力:固定長のバイナリ結果(24バイト = 192ビット)を独自のBase64でエンコードして格納します。結果の文字列表現は一般に 60 文字前後(例:$2b$12$...)になります。
  • 不可逆性:bcryptはハッシュであり暗号化ではないため、適切にハッシュ化されたパスワードから元のパスワードを復元することは実質不可能です。

ハッシュ文字列の形式

典型的な bcrypt ハッシュ文字列は次のようなフォーマットです:

$2b$12$22文字のソルト31文字のハッシュ

  • $2b$:アルゴリズム識別子($2a$, $2b$, $2y$ などのバージョンがある)
  • 12:コストパラメータ(ワークファクタ)
  • 22文字:ソルト(bcrypt独自のBase64表現でエンコード)
  • 31文字:ハッシュ本体(同じく独自Base64)

全体で一般に60文字程度の文字列になります。アルゴリズム識別子とコストがハッシュ文字列に埋め込まれているため、検証時に同じパラメータで照合できます。

歴史的注意点とバージョン差

  • $2a$:最初期に広く使われた識別子。実装依存のバグや文字列処理の違いが議論されたことがあります。
  • $2y$:PHP の古い実装に対応するために導入された識別子(互換性目的)。
  • $2b$:後続の実装での修正を反映した識別子で、現代の実装では推奨されることが多いです。

いずれも、適切なライブラリ/ランタイムであれば下位互換的に検証可能な場合が多いですが、実運用ではライブラリの推奨する識別子とアップデート状況を確認してください。

技術的な特徴と制限

  • 計算コスト制御:コスト(例:10、12、14)を上げることでハッシュ計算時間が増加し、ブルートフォース攻撃が困難になります。運用では「ログイン1回あたりのハッシュ時間(目安 100ms 前後)」を指標にコストを決めるのが一般的です。
  • メモリ使用量:bcrypt は主に CPU で計算コストを稼ぐ設計であり、Argon2 や scrypt のようなメモリハード性(大量のメモリを必要とすることで並列化/ASIC化を困難にする特性)は持ちません。そのため、GPU/ASIC に対する耐性で言えば Argon2 等に劣ります。
  • パスワード長の制限:bcrypt は内部で最大 72 バイト(バイト数)までを扱う実装が一般的で、それを超えるバイトは切り捨てられます。UTF-8 の長いパスフレーズやバイナリデータを扱う場合は注意が必要で、事前に SHA-256 等でハッシュ(プリハッシュ)してから bcrypt に渡す方法が取られます(ただし設計上の注意点あり)。
  • ソルトの利用:ソルトは必ずランダムに且つ十分な長さ(bcrypt 標準の16バイト)で生成してください。固定ソルトや再利用は避けるべきです。

セキュリティ上の運用/実装上のベストプラクティス

  • 公式や信頼できるライブラリ(言語ごとの well-maintained な実装)を使用する。手作り実装は避ける。
  • ソルトは cryptographically secure random(CSPRNG)で生成する。
  • コストは定期的に見直す。ハードウェアの進化に合わせて上げる。まずは目標となる計算時間(例:100ms~500ms 程度)を設定してその計測結果に基づき決定する。
  • パスワード比較はライブラリが提供する検証関数を使い、タイミング攻撃に備えた定時間比較を行う。
  • 長いパスフレーズを許可する場合は、bcrypt の 72 バイト制限を考慮してプリハッシュ(例:SHA-256 で固定長にしてから bcrypt)を行う運用がある。ただしプリハッシュは慎重に行い、ソルトと組み合わせる設計や将来の移行を考慮する。
  • パスワードハッシュだけでなく、レートリミット、多要素認証(MFA)、ログイン試行の監査など多層防御を併用する。

bcrypt と他のハッシュ関数との比較

  • Argon2:2015 年の Password Hashing Competition (PHC) の勝者。メモリハードでGPU/ASIC に強く推奨されている。新規プロジェクトでは Argon2(特に Argon2id)が推奨されることが多い。
  • scrypt:メモリハード設計で GPU に対して比較的強い。Argon2 が登場する以前は高く評価されていた。
  • PBKDF2:古くからある標準的な鍵導出関数。ハードウェアで高速化されやすいため、コストだけでの防御は弱くなりがちだが、FIPS 等の準拠要件がある環境では採用されることがある。
  • 総じて、bcrypt は依然として十分な防御力を持ち、広くサポートされていますが、最先端の攻撃耐性(特に大量並列化・GPU/ASIC対策)の観点では Argon2 の方が優れるとされます。

実運用でよくある疑問と回答

  • Q: bcrypt はまだ安全か?

    A: 適切なコスト設定と実装(正しいライブラリの使用、ソルトの適切な生成)を行えば、現在でも安全かつ実用的です。ただし新規システムでは Argon2 を検討する価値があります。

  • Q: どのコスト値を使えばよいか?

    A: 決まった値はなく、利用環境(サーバー性能、ユーザーロード、許容されるレスポンスタイム)に依存します。実測でログイン処理あたり 100ms 程度を目標にし、許容できる負荷に応じてコストを設定してください(例:12~14 を使う例が多いですが、サーバー性能により変わります)。

  • Q: 72 バイト制限は問題か?

    A: 長いパスフレーズを許容するサービスでは注意が必要です。ユーザーへ適切な上限を通知するか、プリハッシュ(SHA-256 など)してから bcrypt に渡す方法を採ることが一般的です。プリハッシュは実装・移行の影響を考慮して慎重に設計してください。

  • Q: ハッシュのアップグレード(bcrypt -> Argon2)はどうする?

    A: 既存ユーザーのパスワードは平文を保持していないため、一般には「認証成功時に再ハッシュ(rehash)」する戦略が採られます。ログイン時に現在のハッシュが古いアルゴリズム/コストであれば、新アルゴリズムで再ハッシュしてデータベースを書き換えます。

主要言語でのサポート(代表例)

  • PHP: password_hash()/password_verify()(PASSWORD_BCRYPT を使用可能)
  • Python: bcrypt ライブラリ、Passlib(高レベルな抽象)
  • Node.js: bcrypt, bcryptjs(純JS 実装)、bcrypt ライブラリ(ネイティブバインディング)
  • Java: jBCrypt など
  • .NET: BCrypt.Net-Next など

言語ごとに取り扱いや文字列エンコーディングの扱いが異なるため、UTF-8 とバイト数の扱い、NUL バイトの扱いなどをライブラリ仕様で確認してください。

運用チェックリスト(導入前/導入時/導入後)

  • ライブラリは公式か信頼できるプロジェクトを選ぶ。
  • ソルトは自前で保存する必要があるが、多くのライブラリはハッシュ文字列にソルトを含めてくれる(保存はハッシュ文字列だけでよい)。
  • コスト設定は測定・文書化し、定期的に再評価する。
  • ログイン処理でのタイミング攻撃対策、レートリミット、不正検知を実装する。
  • ハッシュアルゴリズムの識別子を保存(bcrypt は文字列に含む)、将来の移行を容易にする。

まとめ(結論)

bcrypt は長年にわたり実績のあるパスワードハッシュ方式で、適切に使用すれば現在でも安全です。特徴として、ソルトの使用、コストパラメータによる計算負荷の調整、固定長のハッシュ出力などがあります。一方でメモリハード性に乏しく、長いパスフレーズに対する内部の長さ制限(72バイト)といった制約があるため、新規プロジェクトや高い並列攻撃耐性を求める場合は Argon2 を検討することを推奨します。どの方式を選ぶにしても、信頼できるライブラリの使用、ソルト管理、適切なコスト設定、そして多層防御(MFA、レート制御など)の併用が重要です。

参考文献