bcrypt - 暗号化全般

暗号化全般 | IT用語集

bcryptとは

bcryptは、1999年にNiels ProvosとDavid Mazièresが発表した論文「A Future-Adaptable Password Scheme」(USENIX Annual Technical Conference 1999)で提案されたパスワードハッシュ関数です。Bruce Schneierが1993年に設計したブロック暗号Blowfishの鍵スケジュール処理を流用し、意図的に低速化させた「Eksblowfish(Expensive Key Schedule Blowfish)」というアルゴリズムを核にしています。単なる暗号化ではなく、パスワードを不可逆な形に変換して保存するための「一方向ハッシュ関数」である点がポイントです。

登場から四半世紀以上が経過した現在も、bcryptはPHPのpassword_hash()関数のデフォルトアルゴリズム、Ruby on Railsのhas_secure_password、Node.jsのbcryptbcryptjsパッケージ、JavaのSpring SecurityにおけるBCryptPasswordEncoderなど、幅広い言語・フレームワークで標準または準標準の位置づけを保っています。専用ハードウェアによる高速化が難しい設計と、長期間の実運用で重大な脆弱性が見つかっていない実績が、今なお選ばれ続ける理由です。

bcryptの最大の特徴はコスト係数(cost factor / work factor)と呼ばれるパラメータで、CPU・GPUの性能向上に合わせて計算コストを指数的に引き上げられる点にあります。ソルトの生成・保存もライブラリ側が自動で行うため、開発者がソルト管理の実装ミスを起こしにくいという運用上の利点も、長年支持されてきた理由の一つです。

bcryptの仕組み(アルゴリズムの内部動作)

bcryptによるハッシュ生成は、大きく3つの工程で構成されています。

1. 鍵スケジュールの初期化(Eksblowfish Setup)

通常のBlowfishは、円周率の値から導出した固定のPボックス・Sボックス(合計4,168バイト)で鍵スケジュールを初期化しますが、bcryptはこの初期化処理にソルト(128ビット/16バイト)パスワードを繰り返し混ぜ込みます。具体的には、まずパスワードとソルトでPボックス・Sボックスを1回初期化し(ExpandKey)、続けてパスワードのみ、ソルトのみを交互に混ぜ込む処理を「2^コスト係数」回繰り返します。この反復こそがbcryptの計算コストの正体であり、コスト係数を1増やすごとに必要な計算量はちょうど2倍になります。

2. 暗号文の生成

初期化済みのEksblowfishの鍵スケジュールを使い、固定の平文"OrpheanBeholderScryDoubt"(24バイト)をECBモードで64回繰り返し暗号化し、192ビット(24バイト)の暗号文を得ます。この平文は開発者が選べる値ではなく、原論文で固定されている点も他のハッシュ関数と異なる特徴です。

3. 出力のエンコード

得られた暗号文は歴史的な実装上の理由で末尾1バイトが切り捨てられ、実質184ビット(23バイト)相当として扱われます。これをbcrypt独自のBase64類似エンコーディング(標準Base64とは文字順序が異なる「Radix-64」に近い方式)で文字列化し、最終的に次のような形式のハッシュ文字列になります。

$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewdBPj/n6oEzD8mq
 │  │  └──────────┬───────────┘└─────────────┬────────────┘
 │  │           ソルト(22文字=128bit)   ハッシュ値(31文字)
 │  └─ コスト係数(12 → 2^12=4096ラウンド)
 └─ アルゴリズムバージョン識別子

バージョン識別子には$2$(オリジナル、非推奨)、$2a$(初期のPHP実装、一部の文字コードで既知の不具合あり)、$2x$$2y$(不具合互換用)、$2b$(2014年にOpenBSDが導入した現行版で、コスト係数によるカウンタオーバーフローの不具合を修正済み)があります。新規実装では必ず$2b$を出力するライブラリを使用すべきです。

具体例:主要言語での実装

AIエンジニアとして認証基盤を構築する際、bcryptは今も第一の選択肢に挙がります。以下は代表的な言語での実装例です。

Python

import bcrypt

# パスワードのハッシュ化
password = b"my_secure_password"
salt = bcrypt.gensalt(rounds=12)  # コスト係数12
hashed = bcrypt.hashpw(password, salt)

# パスワードの検証
if bcrypt.checkpw(password, hashed):
    print("認証成功")

Node.js

const bcrypt = require('bcrypt');
const saltRounds = 12;

// ハッシュ化
const hash = await bcrypt.hash('my_password', saltRounds);

// 検証
const match = await bcrypt.compare('my_password', hash);

PHP

// PHP標準関数(password_hash自体がbcryptを内部利用)
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);

if (password_verify($password, $hash)) {
    echo "認証成功";
}

PHPのpassword_hash()はアルゴリズムを引数で切り替えられる抽象化されたAPIで、内部的に$2y$形式のbcryptハッシュを生成します。将来Argon2idへ移行する場合も、同じ関数のアルゴリズム指定を変えるだけで済むよう設計されている点は実務上の利点です。

メリット・デメリット

観点メリットデメリット・注意点
実績・普及度 25年以上の運用実績があり、既知の致命的な脆弱性が報告されていない。主要言語・フレームワークにほぼ標準搭載されている。 歴史的経緯から実装バージョン($2a$/$2x$/$2y$/$2b$)が乱立しており、互換性の考慮が必要。
ソルト管理 ソルトの生成・保存をライブラリが自動処理するため実装ミスが起きにくい。 ソルト長は128ビット固定で変更できない。
GPU/ASIC耐性 Blowfishの鍵スケジュールはSボックスへの頻繁なランダムアクセスを伴うため、単純なSHA系よりGPU最適化がしづらい。 メモリ使用量は約4KB固定で、Argon2idやscryptのようにGB単位までメモリコストを引き上げることはできない。専用ASICでの高速化リスクはPBKDF2より低いが、Argon2idより高い。
入力長 一般的なパスワードの長さでは実用上問題にならない。 入力は72バイトまでしか処理されず、超過分は無視される(後述)。長いパスフレーズをそのまま使う設計には不向き。
コスト調整 コスト係数を1つ上げるだけで計算量を2倍にでき、ハードウェア性能向上に追従しやすい。 コスト係数を上げすぎるとログイン処理のレイテンシが直接増大し、サーバー負荷やタイムアウトの原因になり得る。

混同されやすい用語・類似技術との違い

bcryptは名前の類似性や役割の近さから、いくつかの用語と混同されがちです。実務で選定を誤らないよう、代表的な違いを整理します。

bcrypt と Blowfish暗号の違い

bcryptは「Blowfish暗号そのもの」ではありません。Blowfishは64ビットブロック暗号(データの暗号化・復号に使う対称鍵暗号)であり、bcryptはその鍵スケジュール処理だけを流用して意図的に低速化した一方向ハッシュ関数です。bcryptで生成した文字列を元のパスワードに復号することはできません。「bcryptで暗号化する」という表現は技術的に不正確で、正しくは「bcryptでハッシュ化する」です。

bcrypt・scrypt・Argon2・PBKDF2の比較

アルゴリズム登場年メモリコスト2026年時点の位置づけ
PBKDF22000年(RFC 2898)固定・小さいFIPS準拠が必要な環境向け。GPU/ASIC耐性はbcryptより低い。
bcrypt1999年約4KB固定既存システムの現役選択肢。新規設計での第一候補ではないが十分実用的。
scrypt2009年可変(MB単位まで拡張可)メモリハード性を重視する用途、暗号通貨のマイニング耐性設計などで採用実績。
Argon2id2015年(PHC優勝)可変(GB単位まで拡張可)OWASP・多くのセキュリティガイドラインが新規実装の第一推奨とする現行標準。

それぞれscryptArgon2PBKDF2の詳細ページも参照してください。混同されやすいポイントとして、「反復回数を増やせば安全性はどれも同じように上がる」という誤解がありますが、bcryptとPBKDF2はCPU計算コストのみを増やす設計であるのに対し、scryptとArgon2はメモリ使用量そのものを増やせる点が本質的に異なります。GPU・ASIC・FPGAは並列演算は得意でもメモリ搭載量には物理的な制約があるため、メモリハードな設計の方が専用ハードウェアによる総当たり攻撃への耐性が高いとされています。

実務ポイント

コスト係数の選び方

コスト係数おおよその処理時間推奨用途
10〜100ms開発・テスト環境
12〜300ms✅ 一般的な本番環境
14〜1秒高セキュリティ要件

コスト係数は「ログイン1回あたり許容できる待ち時間」と「攻撃者が1秒間に試行できる回数」のトレードオフです。実務では、本番サーバーと同等スペックの環境で実測し、ログイン処理全体を200〜500ms程度に収める係数を選ぶのが定石です。定期的にハードウェア性能とベンチマークを見直し、必要に応じてコスト係数を引き上げる運用ルールをあらかじめ決めておくと、値上げのタイミングを逃しません。

トラブル事例と対策

72バイト制限(サイレントな切り詰め)

症状:bcryptは入力を72バイト(UTF-8のマルチバイト文字ではさらに少ない文字数)までしか処理せず、それ以降は静かに無視されます。ユーザーが「73バイト目以降だけ違う」長いパスワードを設定しても、同じハッシュ値が生成されてしまいます。

対策:長いパスワードやパスフレーズを許可するシステムでは、bcryptに渡す前にSHA-256などでプリハッシュ(事前ハッシュ化)してから渡す設計にする、またはArgon2idなど入力長制限のないアルゴリズムを採用する。

低すぎるコスト係数の放置

症状:システム構築当初のコスト係数(8や10など)がハードウェア性能向上後も据え置かれ、相対的な安全性が年々低下する。

対策:ユーザーのログイン成功時に既存ハッシュのコスト係数を検査し、現行の推奨値より低ければその場で再ハッシュ化して保存し直す「段階的移行(rehash on login)」を実装する。

NUL文字による切り詰め

症状:一部の古いCライブラリ由来の実装では、パスワードをNUL終端文字列として扱うため、入力中にNUL文字(\0)が含まれるとそれ以降が切り捨てられる場合があります。

対策:使用しているbcrypt実装のドキュメントで入力データの扱い(バイト列として扱うか、NUL終端文字列として扱うか)を確認し、可能であればメンテナンスが活発な実装(各言語の公式・準公式ライブラリ)を選ぶ。

最新動向(2026年)

標準化の状況とNISTの位置づけ

bcryptはIETFにInternet-Draftとして提出された経緯がありますが、正式なRFCとして標準化されたことはなく、事実上の業界標準(de facto standard)として普及してきたアルゴリズムです。米国NISTのデジタルアイデンティティガイドライン(SP 800-63B)は、記憶シークレット(パスワード)の検証にあたって承認された鍵導出関数(PBKDF2など)を用いること、およびソルトとメモリハードな設計を推奨していますが、bcryptを名指しで承認・非承認としているわけではありません。実務上はOWASPのPassword Storage Cheat Sheetが優先順位付けの指針として広く参照されており、2026年時点でも「新規実装はArgon2id、次点でscrypt、レガシー互換が必要な場合はbcrypt、FIPS準拠が必須の場合はPBKDF2」という序列がおおむね踏襲されています。

Argon2・scryptとの関係の変化

2015年のPassword Hashing Competition(PHC)で優勝したArgon2(特にArgon2id)は、CPU・メモリ両方の耐性を調整できる設計により、多くのセキュリティガイドラインで第一推奨の座を占め続けています。とはいえbcryptは、既存の巨大なユーザーDBを持つシステムやレガシーとの互換性が求められる現場では今も現役です。既存システムをいきなりArgon2idへ全面移行するのではなく、「新規登録・パスワード変更時のみArgon2idで再ハッシュ化し、既存ハッシュはログイン時に段階的に置き換える」移行戦略を取るケースが実務では一般的です。

実務上の推奨事項

  • 新規システム:Argon2id(またはscrypt)を第一候補とし、bcryptは要件上の制約(言語・フレームワークの制限など)がある場合の代替とする。
  • 既存システム:bcrypt自体は引き続き安全に運用可能。コスト係数を定期的に見直し、可能ならログイン時の段階的な再ハッシュ化を仕組み化する。
  • 監査・コンプライアンス:FIPS 140への準拠が求められる環境では、bcryptではなくPBKDF2など承認済みアルゴリズムの利用を検討する。

関連用語

外部リンク・参考資料

📝 関連ブログ記事

【2026年最新】OpenSSLの深刻な問題とは?

よくある質問(FAQ)

Q. bcryptとは何ですか?

bcryptは1999年にNiels Provos・David Mazièresが開発したパスワードハッシュアルゴリズムです。Blowfish暗号を基に設計され、コストパラメータ(work factor)を設定することで計算コストを調整できます。現在もPHP、Node.js、Ruby等の多くのフレームワークのデフォルトパスワードハッシュとして広く使われています。

Q. bcryptのコストパラメータは何を意味しますか?

bcryptのコストパラメータ(work factor)は2^nの計算ラウンド数を意味します。コスト10なら1024ラウンド、コスト12なら4096ラウンドです。OWASPは2024年時点でコスト10以上を推奨し、ハードウェアの向上に合わせて適宜引き上げることを求めています。通常のWebサーバーではコスト10〜12程度が適切です。

Q. bcryptとArgon2はどちらを使うべきですか?

新規システムではArgon2id(OWASPの第一推奨)を使うべきです。bcryptはパスワード長が72バイトに制限されており(72文字以上が切り捨て)、メモリコストが固定(4KB)のため現代のGPU/ASICでのブルートフォースに弱くなっています。ただし、既存のbcryptシステムが適切なコストパラメータで動作していれば、直ちに移行しなくても大きなリスクはありません。

Q. bcryptハッシュの形式はどうなっていますか?

bcryptハッシュは$2b$12$[22文字のソルト][31文字のハッシュ]という形式です。例:$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewdBPj/n6oEzD8mq。$2b$はバージョン(最新)、12はコストパラメータ、その後にBase64エンコードされたソルトとハッシュが続きます。ソルトはbcryptが自動生成するため、開発者がソルトを管理する必要はありません。

Q. bcryptとscrypt、Argon2はどう違いますか?

最大の違いはメモリコストを調整できるかどうかです。bcryptは使用メモリが約4KB固定でCPU計算コストのみを引き上げる設計ですが、scryptとArgon2(特にArgon2id)はメモリ使用量そのものをMB〜GB単位まで拡張できます。GPUやASICはメモリ搭載量に物理的な制約があるため、メモリハードな設計の方が専用ハードウェアによる総当たり攻撃への耐性が高いとされ、OWASPは新規実装でArgon2idを第一推奨としています。

Q. bcryptはBlowfish暗号と同じものですか?

いいえ、異なります。Blowfishはデータを暗号化・復号できる対称鍵ブロック暗号ですが、bcryptはBlowfishの鍵スケジュール処理(Eksblowfish)だけを流用して意図的に低速化した一方向ハッシュ関数です。bcryptで生成した文字列から元のパスワードを復号することはできません。

認証システム構築のご相談