bcrypt の使い方 — Node.jsでパスワードをハッシュ化するライブラリ
一言でいうと
bcrypt は、パスワードをbcryptアルゴリズムでハッシュ化・検証するためのNode.jsネイティブライブラリです。C++バインディングによる高速な処理と、ソルト自動生成によるセキュアなパスワード管理を提供します。
どんな時に使う?
- ユーザー登録時のパスワード保存 — 平文パスワードをハッシュ化してDBに格納する
- ログイン時のパスワード検証 — ユーザーが入力したパスワードとDB上のハッシュを比較する
- 既存システムのパスワードハッシュ移行 —
$2a$/$2b$プレフィックスのbcryptハッシュとの互換性を維持しつつ移行する
インストール
bcrypt はネイティブアドオン(C++)を含むため、ビルド環境として node-gyp とその依存(Python、C++コンパイラ等)が必要です。
# npm
npm install bcrypt
# yarn
yarn add bcrypt
# pnpm
pnpm add bcrypt
注意: macOSではXcode Command Line Tools、WindowsではVisual Studio Build Toolsが必要です。プリビルドバイナリが提供されている場合はビルド不要ですが、対応プラットフォーム(Windows x32/x64、Linux x64、macOS)に限られます。
基本的な使い方
最も一般的なパターンは、async/await を使ったハッシュ化と検証です。
import bcrypt from "bcrypt";
const SALT_ROUNDS = 10;
// パスワードをハッシュ化して保存
async function hashPassword(plainPassword: string): Promise<string> {
const hash = await bcrypt.hash(plainPassword, SALT_ROUNDS);
return hash;
}
// パスワードを検証
async function verifyPassword(
plainPassword: string,
storedHash: string
): Promise<boolean> {
const isMatch = await bcrypt.compare(plainPassword, storedHash);
return isMatch;
}
// 使用例
async function main() {
const password = "mySecureP@ssw0rd";
// 登録時:ハッシュ化してDBに保存
const hash = await hashPassword(password);
console.log("Hash:", hash);
// => $2b$10$N9qo8uLOickgx2ZMRZoMye... のような文字列
// ログイン時:入力パスワードとハッシュを比較
const result = await verifyPassword(password, hash);
console.log("Match:", result); // => true
const wrongResult = await verifyPassword("wrongPassword", hash);
console.log("Match:", wrongResult); // => false
}
main();
よく使うAPI — bcrypt の主要メソッド
1. bcrypt.hash(data, saltOrRounds) — パスワードのハッシュ化(非同期)
最もよく使うメソッドです。第2引数にソルトラウンド数(数値)を渡すと、ソルト生成とハッシュ化を一度に行います。
import bcrypt from "bcrypt";
// saltRoundsを直接指定(推奨)
const hash = await bcrypt.hash("myPassword", 10);
console.log(hash);
// => $2b$10$abcdefghijklmnopqrstuuABCDEFGHIJKLMNOPQRSTUVWXYZ012
// 事前に生成したソルトを渡すことも可能
const salt = await bcrypt.genSalt(12);
const hashWithSalt = await bcrypt.hash("myPassword", salt);
2. bcrypt.compare(data, encrypted) — パスワードの検証(非同期)
平文パスワードとハッシュ値を比較し、一致すれば true を返します。タイミング攻撃に対して安全な実装になっています。
import bcrypt from "bcrypt";
const hash = "$2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy";
const isMatch = await bcrypt.compare("myPassword", hash);
if (isMatch) {
console.log("認証成功");
} else {
console.log("認証失敗");
}
3. bcrypt.genSalt(rounds) — ソルトの生成(非同期)
ハッシュ化に使うソルトを個別に生成します。ラウンド数が大きいほど計算コストが増し、ブルートフォース攻撃への耐性が上がります。
import bcrypt from "bcrypt";
// デフォルトは10ラウンド
const salt10 = await bcrypt.genSalt(10);
console.log(salt10); // => $2b$10$abcdefghijklmnopqrstuv
// セキュリティ要件に応じてラウンド数を上げる
const salt14 = await bcrypt.genSalt(14);
console.log(salt14); // => $2b$14$abcdefghijklmnopqrstuv
4. bcrypt.hashSync(data, saltOrRounds) — パスワードのハッシュ化(同期)
イベントループをブロックするため、CLIツールやスクリプト、テストコードなど限定的な場面で使います。
import bcrypt from "bcrypt";
// 同期版ハッシュ化
const hash = bcrypt.hashSync("myPassword", 10);
console.log(hash);
// 同期版検証
const isMatch = bcrypt.compareSync("myPassword", hash);
console.log(isMatch); // => true
5. bcrypt.getRounds(hash) — ハッシュからラウンド数を取得
既存のハッシュ値がどのラウンド数で生成されたかを確認できます。ラウンド数の移行判断に便利です。
import bcrypt from "bcrypt";
const hash = await bcrypt.hash("myPassword", 12);
const rounds = bcrypt.getRounds(hash);
console.log(rounds); // => 12
// ラウンド数が古い場合に再ハッシュする例
async function rehashIfNeeded(
password: string,
currentHash: string,
targetRounds: number
): Promise<string | null> {
const currentRounds = bcrypt.getRounds(currentHash);
if (currentRounds < targetRounds) {
console.log(`ラウンド数を ${currentRounds} → ${targetRounds} に更新`);
return await bcrypt.hash(password, targetRounds);
}
return null; // 再ハッシュ不要
}
類似パッケージとの比較
| 特徴 | bcrypt | bcryptjs | argon2 |
|---|---|---|---|
| 実装 | C++ネイティブアドオン | 純粋JavaScript | C バインディング (argon2) |
| ビルド環境 | node-gyp 必要 | 不要 | node-gyp 必要 |
| パフォーマンス | ◎ 高速 | △ 遅い(JS実装のため) | ◎ 高速 |
| アルゴリズム | bcrypt | bcrypt | Argon2id/Argon2i/Argon2d |
| セキュリティ強度 | ○ 十分実用的 | ○ 同等 | ◎ 最新推奨(OWASP) |
| 環境依存 | あり(ネイティブ) | なし | あり(ネイティブ) |
| Docker/CI対応 | △ ビルドツール要 | ◎ そのまま動く | △ ビルドツール要 |
| 週間DL数 | 非常に多い | 多い | 増加中 |
選定の目安:
- ビルド環境を用意でき、bcryptで十分なら → bcrypt
- ビルド環境なし・サーバーレス環境 → bcryptjs
- 新規プロジェクトで最高のセキュリティを求めるなら → argon2
注意点・Tips
72バイト制限に注意
bcryptアルゴリズムの仕様上、先頭72バイトまでしか使用されません。72「文字」ではなく72「バイト」である点が重要です。日本語や絵文字はUTF-8で1文字あたり3〜4バイト消費するため、実質的に18〜24文字程度で上限に達します。
// 長すぎるパスワードは事前にバリデーションするのが安全
function validatePasswordLength(password: string): boolean {
const byteLength = Buffer.byteLength(password, "utf-8");
if (byteLength > 72) {
console.warn(`パスワードが72バイトを超えています(${byteLength}バイト)`);
return false;
}
return true;
}
saltRoundsの選定
ラウンド数はコスト係数であり、1増えるごとに計算時間が約2倍になります。
// ラウンド数ごとの処理時間の目安(環境依存)
// 10: ~100ms(一般的なWebアプリ向け推奨値)
// 12: ~300ms
// 14: ~1s
// 16: ~4s
// 本番環境では実測してから決めるのがベスト
import bcrypt from "bcrypt";
async function benchmark(rounds: number): Promise<void> {
const start = Date.now();
await bcrypt.hash("testPassword", rounds);
console.log(`Rounds ${rounds}: ${Date.now() - start}ms`);
}
// 実行例
await benchmark(10);
await benchmark(12);
await benchmark(14);
非同期版を使うこと
hashSync / compareSync はイベントループをブロックします。Webサーバーでは必ず非同期版(hash / compare)を使ってください。bcryptのネイティブ非同期版はワーカースレッドで実行されるため、メインスレッドをブロックしません。
v5.0.0未満は使わない
v5.0.0未満には以下の深刻な脆弱性があります:
- 255文字以上のパスワードが切り詰められる(wrap-around bug)
- NUL文字以降が無視される
必ずv5.0.0以上(現在の最新はv6.0.0)を使用してください。
型定義について
bcrypt v6.0.0にはTypeScriptの型定義が含まれています。別途 @types/bcrypt をインストールする場合はバージョンの整合性に注意してください。
// bcrypt v6 の型(参考)
// hash(data: string | Buffer, saltOrRounds: string | number): Promise<string>
// compare(data: string | Buffer, encrypted: string): Promise<boolean>
// genSalt(rounds?: number): Promise<string>
// getRounds(encrypted: string): number
まとめ
bcrypt は、Node.jsにおけるパスワードハッシュ化のデファクトスタンダードとも言えるライブラリです。C++ネイティブ実装による高速な処理、ソルト自動生成、タイミング攻撃耐性など、パスワード管理に必要な機能が揃っています。72バイト制限やラウンド数の選定に注意しつつ、非同期APIを使えば安全かつ実用的なパスワード管理を実現できます。