bcrypt vs Argon2 vs scrypt:2026 年のパスワードハッシュ徹底比較
結論を先に書く。2026 年に始める新規プロジェクトでは、Argon2id を m=19456, t=2, p=1 で使う。これは OWASP Password Storage Cheat Sheet のベースラインで、現状の出荷可能なパスワードハッシュのなかでは GPU 耐性とサイドチャネル耐性が一番固い。
Argon2 が使えないスタックなら(まれだが、一部の組み込み環境や古いランタイムでは起こる)、scrypt を N=2^17, r=8, p=1 で選ぶ。bcrypt を cost=12 で使うのは、すでに bcrypt のレガシーシステムに縛られていて新しい依存関係を入れられないときだけだ。FIPS-140 準拠が必須の場面では、PBKDF2-HMAC-SHA-256 を 600,000 イテレーションで 使い続ける。
| アルゴリズム | OWASP 2026 パラメータ | 選ぶ場面 |
|---|---|---|
| Argon2id | m=19456 KiB, t=2, p=1 | 新規プロジェクトのデフォルト |
| scrypt | N=2^17, r=8, p=1 | Argon2 が使えない場合 |
| bcrypt | cost=12(最低 10) | レガシーシステム専用 |
| PBKDF2 | HMAC-SHA-256、600k イテレーション | FIPS-140 が必要なとき |
この記事の残りでは、なぜこの数値なのか、自分のハードウェアに合わせてどう調整するか、そしてユーザーにパスワード再設定を強いずに移行する方法を扱う。ベンチマーク用に強いテストパスワードを作りたい場合は、ランダムパスワード生成ツールが便利だ。より広い文脈は、Web 開発者のためのセキュリティベストプラクティスに置いてある。
パスワードハッシュが汎用ハッシュと違う理由
ハッシュ関数は外から見ると同じに見える。データを入れれば固定長のダイジェストが返り、逆算はできない。しかし「4 GB の ISO をハッシュする」設計目標と「12 文字のパスワードをハッシュする」設計目標は正反対だ。前者はシリコンが許す限り速くあるべきで、後者はログイン遅延の予算が許す限り遅くあるべきだ。
この二つを混同すると、データ漏洩はそのままアカウント乗っ取りに変わる。
MD5 や SHA-256 がパスワードに不十分な理由
MD5、SHA-1、SHA-256 のような汎用ハッシュは、スループット目当てに作られている。一般的な CPU で毎秒ギガバイト級、GPU では毎秒数十ギガバイト級が出る。ファイルチェックサムやコンテンツアドレッシングには優秀だが、パスワードには破滅的だ。
2024 年の Hashcat ベンチマークによれば、単一の RTX 4090 で MD5 が約 164 GH/s、SHA-256 が約 22 GH/s を出す。8 文字の小文字英数字パスワード(36^8 ≈ 2.8 × 10^12 通り)は、MD5 なら 1 分弱、SHA-256 でも数分以内に GPU 1 枚で陥落する。sha256(password) を保存している漏洩データベースは、実質的に平文と同じだ。
ソルトでも救えない。ソルトは事前計算済みのレインボーテーブルを無効化するが、アカウント単位の攻撃を遅らせる効果はゼロだ。攻撃者は漏洩したソルトを連結した候補を順番にハッシュするだけで済む。
セキュリティ用途でないチェックサムなら、MD5 と SHA-256 は今でも有用だ。汎用ハッシュ生成ツールはそうした用途のためにある。各アルゴリズムをいつ使うべきかの詳しい比較は、MD5 vs SHA-256 ハッシュアルゴリズム比較を読んでほしい。ただしパスワードに必要なのは、意図的に遅いハッシュだ。
モダンなパスワードハッシュが備えるべき三つの性質
2026 年に出荷する価値のあるパスワードハッシュには、次の三つの性質が必要だ。
- 設計上遅く、ワークファクターを調整できる。 ログインは 100〜500 ms に収めたい。ユーザーが気づかないほど速く、オフライン攻撃者が 100 万回の試行で数日を消費するほど遅く、というバランスだ。ワークファクターはパラメータ化されている必要がある。ハードウェアの進歩に合わせて引き上げるためだ。
- レコード単位のソルト。 パスワードごとに一意なランダムソルトがあれば、レインボーテーブルは無力化され、攻撃者はアカウントを 1 件ずつ攻撃するしかなくなる。モダンなアルゴリズムはソルトを生成し、出力文字列に埋め込んでくれる。
- メモリハード。 GPU や ASIC は計算は速いが、高帯域メモリは高くつく。1 ハッシュあたり数十 MiB を要求するアルゴリズムなら、攻撃者は並列度に比例した RAM を確保するしかなく、GPU ファームのコスト効率が崩れる。
bcrypt は (1) と (2) は満たすが (3) は満たさない。scrypt は三つすべてを満たした最初のアルゴリズムだ。Argon2 はその設計を洗練し、Password Hashing Competition で勝った。次節で順に分解する。
三つのアルゴリズム — アーキテクチャとトレードオフ
bcrypt — Blowfish ベース、時間ハード
bcrypt は 1999 年、Niels Provos と David Mazières が OpenBSD のために作った。Blowfish 暗号を基盤に、コストの高いキーセットアップフェーズ(「EksBlowfish」)を 2^cost 回繰り返す。唯一の調整パラメータはコストファクター(「log rounds」とも呼ばれる)で、1 増えるごとに作業量が倍になる。cost=10 のハッシュは 1,024 回のキースケジュールを実行し、cost=14 では 16,384 回になる。
bcrypt のハッシュは次のような形だ。
$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
│ │ │ │
│ │ │ └─ 31 文字の base64 ハッシュ
│ │ └─ 22 文字の base64 ソルト
│ └─ コストファクター(12)
└─ アルゴリズム識別子($2b$ = bcrypt v2)
このフォーマットは自己記述的だ。verify() は保存された文字列からコストとソルトを読み取るので、別カラムは要らない。
弱点も実在する。bcrypt のメモリフットプリントは約 4 KiB で、これはハイエンド GPU が数千個の bcrypt コアを並列に走らせられるほど小さい。さらに bcrypt は 入力を 72 バイトで暗黙に切り詰める。100 文字のパスフレーズは、その先頭 72 バイトと同じセキュリティしか持たない。最大コストは 31 だが、一般的なハードウェアでは 16 を超えるとログイン遅延が苦しくなり始める。
scrypt — メモリハードのパイオニア
scrypt は 2009 年に Colin Percival が Tarsnap バックアップサービス向けに発表し、2016 年に RFC 7914 として標準化された。メモリハードネスという概念を持ち込んだアルゴリズムで、大きなバッファを擬似乱数データで埋め、ランダムな位置から読み出すことで、実装が実際にメモリを確保するしかない構造になっている。
scrypt は三つのパラメータを取る。
- N — CPU/メモリコスト(2 のべき乗でなければならない)
- r — ブロックサイズ(バイト単位、メモリと攪拌ラウンドの倍率)
- p — 並列度(独立した計算、主にメモリ量を増やさず CPU 時間をスケールさせる用途)
メモリ使用量はおよそ 128 × N × r バイト。OWASP 推奨の N=2^17, r=8 なら 128 × 131072 × 8 = 134,217,728 バイト、すなわち 1 ハッシュあたり 128 MiB ちょうどになる。
scrypt はパスワードハッシュであるだけでなく、鍵導出関数(KDF)でもある。暗号通貨ウォレット、フルディスク暗号化、初期の Litecoin の Proof of Work などで使われている。この二役は、パスワード保管と鍵導出を 1 つのライブラリで済ませたいときに便利だ。
Argon2(id/i/d) — Password Hashing Competition の勝者
Password Hashing Competition は 2013 年から 2015 年にかけて開催され、メモリハードネス、サイドチャネル耐性、実装の単純さを基準に 24 件の候補アルゴリズムを評価した。勝者は Argon2 で、2021 年に RFC 9106 として標準化された。
Argon2 には三つのバリアントがある。違いは攪拌時のメモリ参照方法に集約される。
- Argon2d はデータ依存のメモリアドレスを使う。GPU や ASIC への耐性は最大化されるが、キャッシュタイミングのサイドチャネルから情報が漏れる。暗号通貨の Proof of Work には向くが、認証には向かない。
- Argon2i はデータ非依存のアドレスを使う。サイドチャネルには安全だが、GPU トレードオフ攻撃にはわずかに弱い。
- Argon2id はハイブリッドだ。最初のパスの前半は Argon2i 方式(サイドチャネル安全)、残りは Argon2d 方式(GPU 耐性)でアドレスを決める。RFC 9106 はパスワードハッシュには Argon2id を明示的に推奨しており、OWASP も同じだ。
Argon2 は三つのパラメータを取る。
- m — メモリ量(KiB 単位)
- t — 時間コスト(メモリバッファを通過する回数)
- p — 並列度(同時に処理されるレーン数)
Argon2id のハッシュは PHC 文字列フォーマットで、次のような形だ。
$argon2id$v=19$m=19456,t=2,p=1$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG
bcrypt と同じく、すべてのパラメータが文字列に埋め込まれているので、verify() 側にパラメータ表は要らない。
OWASP 2026 推奨パラメータ
OWASP Password Storage Cheat Sheet が正典のリファレンスだ。以下の数値は最新ガイダンスに合わせてある。保守的な値で、ログイン遅延予算が 100〜500 ms 程度の典型的な Web サーバーを想定して算出されている。出荷前に自社のハードウェアでベンチマークを取るのは前提だ。
Argon2id のパラメータ:第一の選択肢
OWASP のベースライン推奨は m=19456(19 MiB), t=2, p=1 だ。
サーバーに RAM の余裕がもっとあるなら、メモリと時間で作業配分を調整できる。RFC 9106 は等価なプロファイルを公開しており、OWASP は次のいずれも推奨している。
| memoryCost (m) | timeCost (t) | parallelism (p) | RAM/ハッシュ |
|---|---|---|---|
| 47104 | 1 | 1 | 46 MiB |
| 19456 | 2 | 1 | 19 MiB(ベースライン) |
| 12288 | 3 | 1 | 12 MiB |
| 9216 | 4 | 1 | 9 MiB |
| 7168 | 5 | 1 | 7 MiB |
チューニングの手順。 まずピーク時の同時ログイン RAM 予算をもとに m を決める。同時ログイン 100 件想定で 4 GiB を割り当てられるなら、1 ハッシュ 40 MiB だ。次に、本番 CPU で 1 回の verify が 100〜500 ms に収まるまで t を引き上げる。マルチコアを使う具体的な理由がない限り p=1 のままでよい(ほとんどの Web フレームワークはリクエストごとに専用スレッドを割り当てている)。
scrypt のパラメータ:Argon2 が使えないとき
OWASP の推奨は N=2^17(131072), r=8, p=1 で、1 ハッシュあたり 128 MiB を使う。
同時ログインごとに 128 MiB がサーバーに重すぎるなら、OWASP は弱めのプロファイルも認めている。
| N | r | p | RAM/ハッシュ |
|---|---|---|---|
| 2^17 | 8 | 1 | 128 MiB(推奨) |
| 2^16 | 8 | 1 | 64 MiB |
| 2^15 | 8 | 1 | 32 MiB |
N は 2 のべき乗でなければならない。r を増やすとメモリと CPU の作業量が比例して増え、p を増やすとインスタンスごとのメモリは増えずに CPU 作業だけ増える。パスワードハッシュ用途では r と p はデフォルトのままにし、N だけ調整するのがよい。
bcrypt:レガシーのみ、コストファクター 10 以上
OWASP はもう bcrypt を新規プロジェクトには推奨していない。それでも依然として至るところで使われている。Devise、Spring Security、ASP.NET Identity、そして無数の自作認証システムが bcrypt をデフォルトにしている。
bcrypt から逃げられないときのルールは次の通りだ。
- bcrypt の最低コストファクターは 10。 10 未満だと、漏洩データベースが GPU 1 枚で数日のうちに片付くほど速い。
- 推奨は 12〜14、ハードウェア次第。最近の x86 サーバーでは
cost=12が 1 ハッシュあたり約 250 ms、cost=13で 500 ms ほどになる。 - 本番ハードウェアで 1 verify あたり 100〜300 ms を狙う。推測ではなくベンチマークを取る。
- 72 バイトの入力上限を忘れない。ユーザーがパスフレーズを選べるなら、SHA-256 で前処理する(FAQ を参照)。
bcrypt の GPU 耐性は 4 KiB のメモリフットプリントで頭打ちになる。bcrypt のコストをいくら上げても Argon2id のメモリハードネスには届かない。可能なら Argon2id を選ぶこと。
実例として、2024 年式 EPYC サーバーでは bcrypt(cost=12) がおよそ 250 ms、ハイエンドノートでは 350 ms 近くに収まる。もし数値が 100〜500 ms から一桁ずれているなら、ライブラリが本当にネイティブの bcrypt を使っているか、それとも遅い JavaScript のポリフィルにフォールバックしていないかを確認したほうがよい(一部のバンドラーはサーバーレスビルドでネイティブ依存を取り除いてしまう)。
PBKDF2:FIPS-140 準拠の経路
PBKDF2(RFC 8018)はセキュリティガイダンスの最終手段だ。bcrypt より古く、メモリハードでもなく、上記三者のどれよりも GPU 攻撃に早く倒れる。しかし唯一 FIPS-140 認証を受けたパスワードハッシュプリミティブで、連邦政府、医療の HIPAA、特定の金融分野では重要になる。
PBKDF2 が必要なときの使い方は次のとおり。
- PRF として HMAC-SHA-256 を使う(SHA-1 不可、HMAC を介さない素の SHA-256 も不可)
- 最低 600,000 イテレーション(OWASP 2026 のベースライン)
- パスワードごとに 少なくとも 16 バイトのランダムソルト
FIPS が適用されないなら Argon2id を選ぶ。PBKDF2 の固定出力・固定メモリ設計のせいで、攻撃者が GPU シリコンに 1 ドル投じるごとに、毎秒のパスワード試行回数がそのまま増えていく。
NIST の SP 800-63B は PBKDF2-HMAC をパスワードハッシュ用途として「承認」しているが、メモリハードな代替手段より推奨するとは言っていない。これは「PBKDF2 を廃止すれば従来の政府配備がすべて無効になるから許容している」のであって、「新規プロジェクトに最善だから」ではないと読み取るのが正しい。
判断フレームワーク:どのアルゴリズムを選ぶか
比較表
| 観点 | bcrypt | scrypt | Argon2id | PBKDF2 |
|---|---|---|---|---|
| メモリハード | 否 | 是 | 是 | 否 |
| GPU 耐性 | 中 | 高 | 非常に高い | 低 |
| サイドチャネル耐性 | 中 | 中 | 高(id) | 中 |
| パラメータの複雑さ | 1(cost) | 3(N, r, p) | 3(m, t, p) | 1(イテレーション) |
| ライブラリの成熟度 | 優秀 | 良 | 良 | 優秀 |
| 入力長制限 | 72 バイト | なし | なし | なし |
| 標準化 | 事実上の標準 | RFC 7914 | RFC 9106 | RFC 8018 |
| OWASP 2026 ステータス | レガシー専用 | 代替案 | 第一の選択肢 | FIPS のみ |
デフォルトでは Argon2id
新規プロジェクト — 典型的な Web アプリ、モダンな Node/Python/Go/Rust/JVM スタック、FIPS の制約なし — であれば、Argon2id を m=19456, t=2, p=1 で使う。GPU・サイドチャネル耐性が現状一番強く、ライブラリのアップグレードを跨いでも生き残るパラメータ埋め込みフォーマットが手に入り、入力長の落とし穴もない。エコシステムも成熟している。npm の argon2、PyPI の argon2-cffi、golang.org/x/crypto/argon2、crates.io の argon2 クレート、いずれも保守とベンチマークが続いている。
scrypt や bcrypt を選ぶ場面
scrypt を選ぶのは、ランタイムで Argon2 が使えないとき(2026 年では本当にまれ — Cloudflare Workers や Deno でも使える)、あるいは scrypt ベースのシステムがすでに本番稼働中で、移行コストがセキュリティの差分を上回る場合だ。scrypt は今でも堅実なアルゴリズムで、ただ Argon2id ほどのサイドチャネル仕上げを欠いているだけだ。
bcrypt を選ぶのは、レガシーシステムを保守していて、依存関係を最小化する厳しい要件がある(ネイティブコード禁止、追加パッケージ禁止)うえで、72 バイト入力制限がユーザー層的に許容できる場合だ。bcrypt はインターネット規模で 20 年配備され続けており、その故障モードはよく理解されている。
PBKDF2 を選ぶのは、規制当局がそう言うときだけだ。それ以外の理由はない。監査担当者が Argon2id を受け入れるなら(FIPS 対象外のワークロードで認める例は増えている)、Argon2id を選ぶこと。
避けたい典型的なミス
過去 10 年のパスワード保管に絡む侵害事例の多くは、繰り返し起こる少数のエンジニアリングミスに由来する。どれも特殊なものではなく、以下のリストを片手に認証コードを見直せば全部捕まえられる。
- 生の SHA-256 や MD5 でパスワードをハッシュする。 これがパスワード保管の最大の失敗だ。なぜパスワードに使うべきでないかは MD5 vs SHA-256を参照。
- 全ユーザーで一つのグローバルソルトを使い回す。 ソルトはレコード単位で一意でなければならない。Argon2 と bcrypt は自動生成してくれる。それを上書きしてはいけない。
- ハッシュ時間を 50 ms 未満に設定する。 ユーザーには感知できない速度向上のために、セキュリティを差し出した状態だ。100〜500 ms を目指す。
- ハッシュ時間を 1 秒超に設定する。 自社のログインエンドポイントに対する DoS ベクトルを作ったことになる。約 500 ms で打ち止めにする。
- クライアント側でハッシュしてダイジェストをサーバーに送る。 その瞬間、ハッシュこそが新しいパスワードになる。データベースを盗んだ者は、逆算しなくても認証を通せる。常にサーバーでハッシュすること。
- アルゴリズムのパラメータを別カラムに保存する。 PHC 文字列フォーマットがハッシュに埋め込んでくれる。それを使う。
- エラー処理中にパスワードやハッシュをログに出す。 どちらもユーザーのものであって、ログ集約基盤のものではない。リクエスト解析層でロガーに到達する前に消し込む。
verify()の例外を認証失敗として扱う。 壊れた保存ハッシュに対して例外を投げるライブラリは、エラーを表に出すべきで、「パスワード違い」へ静かにフォールスルーさせてはならない。「パスワード違い」(401 を返す)と「保存ハッシュが破損」(500 を返してオンコールにページ送信)は区別する。
実装の現場
Node.js での Argon2id
argon2 パッケージ(リファレンス実装へのネイティブバインディング)が Node の正典だ。
import argon2 from 'argon2';
// サインアップやパスワード変更時のハッシュ化
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 19456, // 19 MiB
timeCost: 2,
parallelism: 1,
});
// → '$argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>'
// ログイン時の検証
const ok = await argon2.verify(hash, candidate);
if (!ok) throw new Error('Invalid credentials');
// 古いパラメータを検出し、ログイン成功時に再ハッシュ
if (argon2.needsRehash(hash, { type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1 })) {
const upgraded = await argon2.hash(candidate, {
type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1,
});
await db.users.update({ id: user.id }, { password_hash: upgraded });
}
needsRehash の一手が長期移行の肝だ。ログインが成功するたびに、ユーザーを煩わせずに保存ハッシュを現行パラメータへアップグレードするチャンスに変わる。
Python で argon2-cffi を使う場合も同じパターンだ。
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher(memory_cost=19456, time_cost=2, parallelism=1)
# ハッシュ化
stored = ph.hash(password)
# 検証
try:
ph.verify(stored, candidate)
except VerifyMismatchError:
raise ValueError('Invalid credentials')
# パラメータ更新時の再ハッシュ
if ph.check_needs_rehash(stored):
stored = ph.hash(candidate)
Go で golang.org/x/crypto/argon2 を使う場合。
import (
"crypto/rand"
"golang.org/x/crypto/argon2"
)
func hashPassword(password string) ([]byte, []byte) {
salt := make([]byte, 16)
rand.Read(salt)
hash := argon2.IDKey([]byte(password), salt, 2, 19456, 1, 32)
return hash, salt
}
Go 標準ライブラリは PHC フォーマットのエンコーダを同梱していない。argon2.IDKey プリミティブを直接使うときは、パラメータとソルトをハッシュとともにエンコードする責任は自分にある。多くの Go プロジェクトはそのために github.com/alexedwards/argon2id のようなラッパーを使っている。
Rust の argon2 クレートも同じくらい慣用的に書ける。
use argon2::{Argon2, PasswordHasher, PasswordVerifier, password_hash::{SaltString, rand_core::OsRng}};
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default(); // デフォルトで Argon2id, m=19456, t=2, p=1
let hash = argon2.hash_password(password.as_bytes(), &salt)?.to_string();
// 検証時
let parsed = argon2::password_hash::PasswordHash::new(&hash)?;
argon2.verify_password(candidate.as_bytes(), &parsed)?;
これら 3 つのランタイムで生成される文字列は相互に互換だ。Node で作ったハッシュを Python や Rust できれいに検証できる。このクロスランタイム互換性は、アルゴリズム固有のラッパーよりも Argon2 が多言語アーキテクチャで安全な選択肢になる理由の一つだ。
bcrypt から Argon2id への移行パターン
users テーブルを丸ごと消して一からやり直せる場面は、まずない。正しい移行パターンは、ハッシュ生成ツール FAQ の MD5 から bcrypt への節で扱っているのと同じ — ログインを駆動とするソフトなアップグレードだ。
アルゴリズムを追跡するカラムを足す。
ALTER TABLE users ADD COLUMN password_algo VARCHAR(16) NOT NULL DEFAULT 'bcrypt';
ログイン時、適切な検証ロジックへ振り分ける。
async function verifyAndMaybeRehash(user, candidate) {
let ok;
if (user.password_algo === 'argon2id') {
ok = await argon2.verify(user.password_hash, candidate);
} else if (user.password_algo === 'bcrypt') {
ok = await bcrypt.compare(candidate, user.password_hash);
if (ok) {
// レガシー検証成功 → Argon2id で再ハッシュ
const newHash = await argon2.hash(candidate, {
type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1,
});
await db.users.update({ id: user.id }, {
password_hash: newHash,
password_algo: 'argon2id',
});
}
}
return ok;
}
サンセット期間は 6〜12 か月 を取る。9 か月目に「お客様のパスワードは古い方式で保管されています。アップグレードのため再ログインしてください」というメールを送る。12 か月後、まだ bcrypt のままのアカウントは次回ログイン時に強制パスワードリセットを要求する。アクティブユーザーは透過的に移行し、休眠アカウントだけが一度だけ摩擦を体験する。
scrypt や PBKDF2 から移行する場合も同じパターンが効く。必要な状態は password_algo カラムだけだ。
ペッパー、長さ制限、エンコードの落とし穴
実運用に噛みつく鋭い角がいくつかある。
ペッパー。 ペッパーはハッシュ前にすべてのパスワードへ加えるアプリケーションレベルの秘密値で、データベースとは別の場所(KMS、環境変数、Hashicorp Vault など)に保管する。データベースが漏れてもアプリの秘密値が漏れなければ、ペッパーがない限り漏洩ハッシュは攻撃不能になる。連結ではなく HMAC として適用する。
import { createHmac } from 'crypto';
const peppered = createHmac('sha256', process.env.PEPPER).update(password).digest();
const hash = await argon2.hash(peppered, { type: argon2.argon2id, /* ... */ });
ペッパーのローテーションは滅多にやらない(再ハッシュが必要だから)が、バージョニングしてローテーション可能にしておく。PEPPER_V2 を使い、検証時に PEPPER_V1 へフォールバックする、というかたちだ。
bcrypt の 72 バイト制限。 bcrypt を使うしかなく、なおかつ任意長のパスワードをサポートしたい場合、SHA-256 で前処理し base64 エンコードする(bcrypt が一貫性を欠く扱いをする埋め込み NUL バイトを避けるため)。
import { createHash } from 'crypto';
const prepped = createHash('sha256').update(password, 'utf8').digest('base64');
const hash = await bcrypt.hash(prepped, 12);
検証時にも同じ prepped 変換をかける必要がある。認証コードに巨大なコメントとして書き残しておこう。未来の自分が現在の自分に感謝する。
UTF-8 正規化。 文字列 "café" は c-a-f-é(4 コードポイント、NFC)とも、c-a-f-e + 結合アキュート(5 コードポイント、NFD)とも符号化できる。見た目は同じでも、生成されるハッシュは違う。ハッシュ前に必ず NFC へ正規化する。
const normalized = password.normalize('NFC');
これは想像以上の頻度で、モバイルキーボードや PDF からのコピー&ペーストに噛みつく。
クライアントで事前ハッシュしない。 クライアントが計算したハッシュをサーバーへ送ると、それが新しいパスワードになる。データベースを読んだ者は誰でも認証を通せる。サーバーでハッシュする、以上。JWT もこれを変えはしない — JWT が認証するもの・しないものについては JWT トークンをデコードする方法を見てほしい。
ベンチマークはノート PC ではなく本番ハードウェアで取る。 13 世代 Intel ノートで Argon2id を m=19456, t=2, p=1 で動かすとおよそ 35 ms で完了する。同じパラメータが t3.small EC2 インスタンスでは 180 ms 近く、Raspberry Pi 4 では 600 ms 超になる。実際に本番を動かすハードウェアを選び、1,000 回 verify を計測して中央値からチューニングする。サーバーレスのコールドスタートによるログイン遅延のばらつきも測る価値がある — Lambda のコールドスタートはハッシュとは無関係に 200〜800 ms を上乗せしうる。
FAQ
パスワードハッシュと暗号化の違いは?
ハッシュは一方向だ。固定長のフィンガープリントを計算するが、入力に戻すことはできない。暗号化は双方向で、正しい鍵があれば元に戻せる。パスワードは暗号化ではなくハッシュ化しなければならない。サーバーがいかなるユーザーのパスワードも復元できないようにする。そうすれば、データベース漏洩がそのまま資格情報漏洩にはならない。
パスワードに SHA-256 をそのまま使えない理由は?
SHA-256 は速度のために設計されている。最新の GPU は毎秒 220 億回の SHA-256 を計算するので、漏洩データベースに含まれる 8 文字小文字パスワードは数分で陥落する。パスワードハッシュに必要なのは、SHA-256 が持っていない三つの性質、すなわち意図的に遅い実行、レコード単位のソルト、メモリハードネスだ。トレードオフの原則は、当社のハッシュ生成ツールの「セキュリティに MD5 を使うな」ガイダンスで説明したものと同じ — 弱いハッシュを攻撃者がどうやって平文に変えるかはパスワードエントロピー解説で詳しく読める。
bcrypt は 2026 年でも安全か?
bcrypt 自体は破られていない。Blowfish ベースのキースケジュールは暗号学的に健全なままだ。変わったのは脅威モデルだ。GPU と ASIC の存在が、bcrypt のメモリハードネス欠如を Argon2id と比較した際の意味のある弱点に変えた。OWASP の 2026 年スタンスは、cost ≥ 10 の bcrypt はレガシーシステムでは許容できるが、新規プロジェクトは Argon2id を選ぶべきだ、というものだ。
Argon2i・Argon2d・Argon2id のどれを使うか?
Argon2id を使う。RFC 9106 はパスワードハッシュ向けの推奨バリアントとして Argon2id を指定している。Argon2i はデータ非依存(サイドチャネル安全だが GPU トレードオフ攻撃には弱い)、Argon2d はデータ依存(GPU には強いがキャッシュタイミングのサイドチャネルに脆弱)だ。Argon2id はその両方の性質を一発で得られるハイブリッドだ。
自分のアプリ向けに Argon2id のパラメータをどう選ぶか?
OWASP のベースライン m=19456, t=2, p=1 から始める。本番 CPU でベンチマークして調整する。
- ログインあたりの RAM 予算を決める(例:ピーク同時実行で 50 MiB)。
mをその値以下に設定する。argon2.hash()をループで実行し、ウォール時間を計測する。- 中央値が 100〜500 ms に入るまで
tを増やす。
マルチレーン並列がランタイムを助けるとプロファイリングで確認できない限り、p=1 のままにする。高トラフィックの認証サーバーでは、t を上げて m を下げる方向に振ると RAM の余裕が増えることが多い。
bcrypt の 72 バイト制限とは? 長いパスフレーズはどう扱う?
bcrypt は入力を Blowfish のキースケジュールへ送り、これが 72 バイトで切り捨てられる。150 文字のパスフレーズは先頭 72 バイトと同じセキュリティしか持たず、残りは無視される。対処は、SHA-256(32 バイト)または SHA-512(64 バイト)で前処理し、ダイジェストを base64 エンコードして NUL バイトを避けたものを bcrypt に渡すことだ。Argon2id と scrypt にこの制限はなく、任意の長さの入力をそのまま受け取る。
パスワードリセットを強制せずに bcrypt から Argon2 へ移行できるか?
できる。パターンは、password_algo カラムの裏で両方のアルゴリズムを保存し、検証を適切なライブラリへ振り分け、bcrypt 検証が成功するたびにすぐ Argon2id で再ハッシュして行を更新する、というものだ。アクティブユーザーは通常のログイン頻度のなかで黙って移行していく。休眠アカウント向けには 6〜12 か月のサンセット期間を取り、その後もまだ bcrypt のレコードはパスワードリセットを強制する。同じパターンは任意のアルゴリズム間移行に使える。
2026 年でも PBKDF2 はよい選択肢か?
FIPS-140 準拠が必要なときだけだ — 連邦政府、規制下の医療(HIPAA)、特定の金融システムが典型だ。PRF として HMAC-SHA-256 を使い、最低 600,000 イテレーションにする。PBKDF2 はメモリハードでないので、同等の遅延予算で見ると Argon2id より早く GPU 攻撃に倒れる。FIPS が適用されないなら、Argon2id を選んでコンプライアンスの曲芸はスキップしよう。
2026 年のパスワードハッシュの答えは短い。デフォルトは Argon2id を OWASP のベースラインパラメータで、Argon2 が使えなければ scrypt にフォールバック、bcrypt はレガシーが要求する場面のみ、PBKDF2 は FIPS 縛りのシステム向けに取っておく。ハッシュにはレコード単位のソルト(モダンなライブラリは自動で扱う)、データベース外に保管されるアプリケーションレベルのペッパー、そしてハードウェアの進歩に合わせてワークファクターを引き上げられるログイン駆動の再ハッシュループを組み合わせる。
代表的なパスワードセットをランダムパスワード生成ツールで作り、本番 CPU に対して verify パスをベンチマークし、決めたパラメータを定数ファイルへ書き残しておこう。次のエンジニアが 2028 年に何を上げるべきか、ひと目でわかるように。完全なセキュリティ文脈 — TLS、セッション管理、レート制限、MFA — はWeb 開発者のためのセキュリティベストプラクティスに置いてある。