AES、RSA、あるいはSHA-256を使ってユーザーのパスワードを保存している場合、それは間違っています。わずかな間違いではなく、根本的な間違いです。これはウェブ開発における最も一般的なセキュリティミスであり、解決策は、bcryptのような適切なパスワードハッシュ関数を使うことです。
なぜそれが重要なのか、bcryptがどのように機能するのか、そして生産環境向けのコードの例を紹介します。
パスワードが特別な扱いを必要とする理由
ほとんどの暗号化操作は設計上、 逆転可能または高速であるように作られています。暗号化は設計上逆転可能であることがその目的です。SHA-256やMD5は高速で、毎秒ギガバイトを処理できます。これらの特性はパスワードにとって災害的です。
攻撃者があなたのデータベースにアクセスした場合、パスワードハッシュが得られます。暗号化では、キーを発見すればすべてを復号できます。MD5やSHA-256のような高速ハッシュでは、攻撃者はGPUを活用したブートフォース攻撃を実行できます——現代のハードウェアは毎秒 数百億 のMD5ハッシュをテストできます。あなたの「複雑な」パスワードは数分でクラックされます。
パスワードは以下の3つの条件を満たす必要があります:
- 逆転不可能 — キーまたはアルゴリズムを持っていても、明文を復元できません
- 計算が遅い — 意図的な遅さは、ブートフォース攻撃を実行するのを困難にします
- ユーザーごとに一意 — 同じパスワードが異なるハッシュを生成しなければなりません
bcryptはすべての条件を満たしています。一般的なハッシュ関数や暗号化はそれらを満たしません。
ハッシュングと暗号化とパスワードハッシュの違い
これらは交換可能ではありません:
| アプローチ | 逆転可能? | 高速? | パスワードに安全? |
|---|---|---|---|
| 暗号化(AES、RSA) | キーがある場合、はい | はい | いいえ |
| 高速ハッシュ(MD5、SHA-256) | いいえ | はい(設計上) | いいえ |
| パスワードハッシュ(bcrypt、Argon2id) | いいえ | いいえ(設計上) | はい |
暗号化の逆転可能性は致命的です:キーが漏洩すればすべてのパスワードが危険になります。高速ハッシュも致命的です:速度はブートフォース攻撃を可能にします。パスワードハッシュ関数は意図的に遅いように設計されており、それが目的です。
bcryptがどのように機能するか
1999年にNiels ProvosとDavid Mazièresによって設計されたbcryptは、以下の3つの点が重要です:
1. サルトの生成。 ハッシュ処理前にbcryptはランダムなサルト(16バイト)を生成し、ハッシュ出力に含まれます。2人のユーザーが同じパスワードを共有していても、ハッシュは異なります。これにより、事前に計算されたリーンテーブル攻撃は完全に防げます。
2. ワークファクター(コスト)。 bcryptはコストパラメータ(通常10~14)を受け取ります。コストの増加は計算時間の2倍になります。コスト12では、現代のハードウェアで約250~400msの計算時間がかかります。ログインリクエストに対してこれはまったく気にならないほど遅いですが、10億回の試行攻撃を数十年にわたる操作に変換します。
3. 出力は自立しています。 bcryptハッシュは $2b$12$... のように見え、アルゴリズムバージョン、コストファクター、サルト、およびハッシュをすべて含んでいます。別途サルトカラムを必要とせず、全体の文字列を保存してください。
生産環境向けコード:Node.jsとPython
Node.js(bcryptjsまたはbcrypt)
const bcrypt = require('bcrypt');
const SALT_ROUNDS = 12;
// Hash a password
async function hashPassword(plaintext) {
return bcrypt.hash(plaintext, SALT_ROUNDS);
}
// Verify a password against a stored hash
async function verifyPassword(plaintext, storedHash) {
return bcrypt.compare(plaintext, storedHash);
}
// Usage
const hash = await hashPassword('hunter2');
// Store `hash` in your database
const isValid = await verifyPassword('hunter2', hash);
// true
Python(bcrypt)
import bcrypt
COST = 12
def hash_password(plaintext: str) -> bytes:
salt = bcrypt.gensalt(rounds=COST)
return bcrypt.hashpw(plaintext.encode('utf-8'), salt)
def verify_password(plaintext: str, stored_hash: bytes) -> bool:
return bcrypt.checkpw(plaintext.encode('utf-8'), stored_hash)
# Usage
hashed = hash_password('hunter2')
# Store hashed in your database
is_valid = verify_password('hunter2', hashed)
# True
完全なハッシュ文字列を保存してください。明文を保存せず、サルトを別途保存せず、中間値を保存しないでください。
ワークファクターの選定
適切なコストファクターは、あなたのハードウェアに依存します。目標は、生産サーバー上で各ハッシュ操作が 200~500ms かかるようにすることです。これはユーザー体験に十分に速く、攻撃者にとって遅いという点で、非常に適しています。
現在の推奨: コスト12 として最低限、 14 高価値アカウント(管理者、金融)にはコスト14を使用。実際のハードウェア上でベンチマークを実行:
// Node.js: benchmark different cost factors
const bcrypt = require('bcrypt');
for (let cost = 10; cost <= 14; cost++) {
const start = Date.now();
await bcrypt.hash('benchmark', cost);
console.log(`Cost ${cost}: ${Date.now() - start}ms`);
}
コスト12が100ms未満の場合、増加します。コスト14が1000msを超える場合、13に下げます。毎年見直してください——ハードウェアが進化し、コストファクターもそれに合わせるべきです。
あなたは IO Tools bcryptハッシュ生成器.
bcryptとArgon2idとscryptの比較
bcryptは検証済みで広くサポートされていますが、制限があります:記憶量がハードではないため、専門的なハードウェア(ASICやFPGA)を持つ攻撃者は、記憶量が大きい代替手段よりも並列攻撃を効率的に実行できます。
| アルゴリズム | 記憶量がハード | 並列性に抵抗 | おすすめ |
|---|---|---|---|
| bcrypt | いいえ | 部分的に | 良いデフォルト;コスト≥12を使用 |
| scrypt | はい | 部分的に | bcryptより優れ、ツールが少ない |
| Argon2id | はい | はい | 新しいプロジェクトでは推奨 |
新しいプロジェクトでは: Argon2idを使用してください。2015年のパスワードハッシュコンペティションで勝ち、記憶量がハードであり、GPUおよびASIC攻撃に抵抗し、現在OWASPのトップ推奨となっています。APIはbcryptにほぼ同じです。
既存プロジェクトでは: すでにbcryptに移行しており、適切なコストファクターを持っている場合、移行は緊急ではありません。次の大きなリファクタリング時に追加してください。
一般的な実装ミス
ハッシュを再ハッシュする。 一部の開発者は、クライアントサイドでパスワードをハッシュし、サーバー側で再ハッシュします。クライアントサイドのハッシュが「パスワード」になります。あなたは固定長のhex文字列をハッシュしており、人間が選んだパスワードではなくなります——2重ハッシュで何の損失もありませんが、何の利益も得られず、混乱を引き起こします。
72バイトのトリガム問題。 bcryptは72バイトを超えた部分を無視します。100文字と72文字のパスワードが最初の72文字が同じであれば、bcryptでは同じと見なされます。ユーザーが非常に長いパスワードを設定する場合、これは静的セキュリティの劣化になります。対策:bcryptに渡す前にSHA-256で事前にハッシュし、ただし、その影響を完全に理解し、明確にドキュメント化する必要があります。
弱いワークファクター。 コスト10は2011年に適切でした。2026年には少なくともコスト12を使用してください。既存の記録がコスト10を使用している場合、透明にアップグレードできます:ログイン成功後に確認されたパスワードを新しいコストで再ハッシュし、更新されたハッシュを保存してください。
非同期の重要性。 bcryptはCPU負荷が大きいです。Node.jsでは常に非同期API(上記の例のように)を使用してください。同期的なbcryptをNodeサーバーで使用すると、他のすべてのリクエストがブロッキングされます。
MD5/SHAからbcryptへの移行
元の明文を再ハッシュすることはできません。しかし、機会的に移行できます:
- 新しい
password_hashカラムを古いpassword_md5column - ログイン成功(明文がある場合)時に、bcryptでハッシュし、
password_hashに保存し、古いカラムを削除します - 移行期間の後、ログインしていないユーザーを強制的にパスワードを再設定させます
- 一度
password_md5がすべてのユーザーに対して空である場合、カラムを削除します
これは標準的なアプローチであり、ダウンタイムを必要としません。
結論
データを取得するために暗号化が使われ、データを検証するためにハッシュが使われます。パスワードは検証されるべきであり、取得されるべきではありません——つまり、暗号化は間違ったツールです。
bcryptはサルト、調整可能な遅さ、そして自立したハッシュフォーマットを提供します。25年間、正しい答えでした。コスト12以上を使用し、新しいプロジェクトではArgon2idを使用し、MD5およびSHA-256からできるだけ早く移行してください。
これは現実のリスクです。データベースが攻撃されると、適切にbcryptハッシュされたパスワードは計算的にクラックが困難になります。MD5ハッシュは一夜でクラックされます。
あなたも好きかもしれません
恵 スコアボードが到着しました!
スコアボード ゲームを追跡する楽しい方法です。すべてのデータはブラウザに保存されます。さらに多くの機能がまもなく登場します!
