はじめに
パスワードの強度をチェックする機能は、ユーザー登録フォームやパスワード変更画面でよくみる。
従来の「8文字以上、大文字小文字数字記号を含む」といった単純なルールベースのチェックでは、Password123!のような推測されやすいパスワードも「強い」と判定されてしまう問題がある。
そこで、Dropboxが開発したzxcvbn.jsを使用することで、より実用的なパスワード強度評価を実装できる。zxcvbnは辞書攻撃、パターンマッチング、文字列の繰り返しなど、実際の攻撃手法を考慮した強度判定を行う。
今回は、zxcvbn.jsを使ったパスワード強度チェッカーを実装してみる。
今回の成果物
環境
zxcvbn.js 4.4.2 (CDN経由)
HTML5
CSS3
JavaScript (ES6)準備
zxcvbn.jsの読み込み
CDN経由で簡単に利用できる。
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/zxcvbn.js"></script>npmでインストールする場合
npm install zxcvbnzxcvbn.jsの仕様
基本的な使い方
const result = zxcvbn('password123');
console.log(result);戻り値の構造
zxcvbn関数は以下のような詳細な情報を含むオブジェクトを返す。
score(スコア)
0から4の5段階でパスワードの強度を評価する。
| スコア | 評価 | 説明 |
|---|---|---|
| 0 | 非常に弱い | すぐにクラックされる |
| 1 | 弱い | 短時間でクラックされる |
| 2 | やや弱い | オフライン攻撃で比較的容易にクラック可能 |
| 3 | 普通 | 一定の耐性がある |
| 4 | 強い | クラックが非常に困難 |
feedback(フィードバック)
{
warning: "これは一般的なパスワードです",
suggestions: [
"もう少し長くしてください",
"予測しにくい単語を追加してください"
]
}- warning: 現在のパスワードの主な問題点
- suggestions: 改善のための具体的な提案
crack_times_seconds(クラック時間)
様々な攻撃シナリオでのクラックにかかる時間を秒単位で返す。
{
online_throttling_100_per_hour: 360, // オンライン攻撃(スロットル有り)
online_no_throttling_10_per_second: 0.4, // オンライン攻撃(スロットル無し)
offline_slow_hashing_1e4_per_second: 0.0001, // オフライン攻撃(遅いハッシュ)
offline_fast_hashing_1e10_per_second: 1e-10 // オフライン攻撃(高速ハッシュ)
}crack_times_display(人が読める形式のクラック時間)
{
online_throttling_100_per_hour: "6分",
online_no_throttling_10_per_second: "1秒未満",
offline_slow_hashing_1e4_per_second: "1秒未満",
offline_fast_hashing_1e10_per_second: "瞬時"
}sequence(検出されたパターン)
パスワード内で検出された脆弱なパターンの配列。
[
{
pattern: "dictionary", // 辞書攻撃で見つかる単語
token: "password", // 実際の文字列
matched_word: "password"
},
{
pattern: "sequence", // 連続した文字
token: "123",
sequence_name: "digits"
}
]ユーザー辞書の指定
ユーザー名やメールアドレスなど、個人情報を含むパスワードを検出できる。
const result = zxcvbn('john1234', ['john', '[email protected]']);
// ユーザー名を含むパスワードとして低評価される
実装内容
HTML構造
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>パスワード強度チェッカー</title>
</head>
<body>
<div class="container">
<h1>パスワード強度チェッカー</h1>
<input type="password" id="password" placeholder="パスワードを入力してください">
<div class="strength-bar">
<div id="strengthBar" class="strength-bar-fill"></div>
</div>
<p id="strengthText" class="strength-text"></p>
<p id="feedback" class="feedback"></p>
<label class="show-password">
<input type="checkbox" id="showPassword">
パスワードを表示
</label>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/zxcvbn.js"></script>
<script src="script.js"></script>
</body>
</html>* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #f5f5f5;
}
.container {
background: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
width: 90%;
max-width: 400px;
}
h1 {
font-size: 1.5rem;
margin-bottom: 1.5rem;
color: #333;
}
input[type="password"],
input[type="text"] {
width: 100%;
padding: 0.75rem;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.3s;
}
input[type="password"]:focus,
input[type="text"]:focus {
outline: none;
border-color: #4CAF50;
}
.strength-bar {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
margin: 1rem 0 0.5rem;
overflow: hidden;
}
.strength-bar-fill {
height: 100%;
transition: width 0.3s, background-color 0.3s;
width: 0;
}
/* スコア別の色設定 */
.score-0 { width: 20%; background: #f44336; }
.score-1 { width: 40%; background: #ff9800; }
.score-2 { width: 60%; background: #ffeb3b; }
.score-3 { width: 80%; background: #8bc34a; }
.score-4 { width: 100%; background: #4CAF50; }
.strength-text {
font-size: 0.9rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.feedback {
font-size: 0.85rem;
color: #666;
min-height: 1.2rem;
line-height: 1.4;
}
.show-password {
display: flex;
align-items: center;
margin-top: 1rem;
font-size: 0.9rem;
cursor: pointer;
user-select: none;
}
.show-password input {
margin-right: 0.5rem;
cursor: pointer;
}const passwordInput = document.getElementById('password');
const strengthBar = document.getElementById('strengthBar');
const strengthText = document.getElementById('strengthText');
const feedback = document.getElementById('feedback');
const showPasswordCheckbox = document.getElementById('showPassword');
// スコアに対応するラベル
const strengthLabels = ['', '弱い', 'やや弱い', '普通', '強い'];
// パスワード入力時の処理
passwordInput.addEventListener('input', function() {
const password = this.value;
// 空の場合は初期化
if (!password) {
strengthBar.className = 'strength-bar-fill';
strengthText.textContent = '';
feedback.textContent = '';
return;
}
// zxcvbnでパスワードを評価
const result = zxcvbn(password);
const score = result.score;
// スコアに応じてバーを更新
strengthBar.className = `strength-bar-fill score-${score}`;
strengthText.textContent = `強度: ${strengthLabels[score]}`;
// フィードバックを表示
let feedbackText = '';
if (result.feedback.warning) {
feedbackText = result.feedback.warning;
}
if (result.feedback.suggestions.length > 0) {
feedbackText += (feedbackText ? ' / ' : '') +
result.feedback.suggestions.join(', ');
}
// クラック時間を追加
const crackTime = result.crack_times_display.offline_slow_hashing_1e4_per_second;
if (crackTime) {
feedbackText += feedbackText ? ` (クラックまで: ${crackTime})` :
`クラックまで: ${crackTime}`;
}
feedback.textContent = feedbackText;
});
// パスワード表示/非表示の切り替え
showPasswordCheckbox.addEventListener('change', function() {
passwordInput.type = this.checked ? 'text' : 'password';
});実装に関して
1. リアルタイムフィードバック
inputイベントを使用し、ユーザーが入力するたびにリアルタイムで強度を評価する。
passwordInput.addEventListener('input', function() {
const result = zxcvbn(this.value);
// 即座に結果を反映
});2. フィードバック
スコアに応じて色分けされたプログレスバーを表示し、強度を把握できるようにする。
.score-0 { background: #f44336; } /* 赤 */
.score-4 { background: #4CAF50; } /* 緑 */3. 改善の提案
zxcvbnが提供するfeedback.suggestionsを表示することで、ユーザーがどのようにパスワードを改善すればよいか理解できる。
if (result.feedback.suggestions.length > 0) {
feedbackText = result.feedback.suggestions.join(', ');
}4. クラック時間の表示
offline_slow_hashing_1e4_per_secondを使用して、現実的な攻撃シナリオでのクラック時間を表示する。
const crackTime = result.crack_times_display.offline_slow_hashing_1e4_per_second;パスワード強度の例
実際にさまざまなパスワードを試した結果:
| パスワード | スコア | 評価 | フィードバック |
|---|---|---|---|
password | 0 | 非常に弱い | これは一般的なパスワードです |
Password123 | 1 | 弱い | 予測可能なパターンです |
MyP@ssw0rd! | 2 | やや弱い | 一般的な置き換えは避けてください |
correct horse battery staple | 4 | 強い | クラックまで: 数世紀 |
Tr0ub4dor&3 | 2 | やや弱い | 短すぎます |
J8#mK9$pL2@nQ5 | 4 | 強い | クラックまで: 数世紀 |
zxcvbn.jsについて
メリット
- 辞書攻撃、パターンマッチング、繰り返しなど実際の攻撃を考慮
- 「弱い」だけでなく、改善方法を提示
- 約800KBと比較的小さいファイルサイズ
- サーバーへの通信不要
- 英語以外の辞書も含まれている
デメリット
- CDN経由でも約800KBの読み込みが必要
- 辞書ファイルの読み込み完了まで時間がかかる場合がある
- 日本語の辞書攻撃には完全対応していない
参考
zxcvbn - Low-Budget Password Strength Estimation
https://github.com/dropbox/zxcvbnzxcvbn.js CDN
https://www.jsdelivr.com/package/npm/zxcvbnDropbox Tech Blog - zxcvbn: realistic password strength estimation
https://dropbox.tech/security/zxcvbn-realistic-password-strength-estimationOWASP Authentication Cheat Sheet
https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
おわりに
よくあるパスワード強度チェッカーについて実装してみたく、ライブラリを探してみたところzxcvbn.jsがあったので使用してみた。
単純なルールベースではなく、実際の攻撃手法を考慮した実用的なパスワード強度チェッカーとなっているので良さげ。
ライブラリが返却してくれるレスポンスも手厚いものがあるので、工夫次第で色々できそうなのもよい感じだった。
ユーザー登録フォームやパスワード変更画面に実装することで、アカウントのセキュリティ向上に貢献できると思った。