パスワードに関する一般的なアドバイスの多くは間違っているか、非実用的です。「大文字・小文字・数字・記号を混ぜて使いなさい」という指示は厳格に聞こえますが、P@ssw0rd1!のようなパスワードを生み出します。これはコンピュータにとって簡単に解析できる反面、人間には覚えにくいものです。本ガイドでは本質を解説します。
パスワードの強度を決めるもの
パスワードの強度は根本的にはエントロピーの問題です — 攻撃者がパスワードを見つけるのに必要な試行回数です。
Tr0ub4dor&3のような12文字のパスワードは複雑に見えます。しかし、一般的なパターン(単語+数字置換+記号)に従っているため、ルールベースの変形を使った辞書攻撃でランダムな8文字パスワードより速くクラックされることがあります。
真のランダム性は、見た目の複雑さより常に優れています。
パスワードエントロピー
エントロピーはビット単位で予測不可能性を測ります。1ビット増えるごとに探索空間は2倍になります。
N文字のプールからL文字のパスワードを選ぶ場合:
エントロピー = L × log₂(N)
| 文字プール | プールサイズ | 12文字のエントロピー |
|---|---|---|
| 小文字のみ | 26 | 56ビット |
| 小文字+大文字 | 52 | 68ビット |
| 小文字+大文字+数字 | 62 | 72ビット |
| 小文字+大文字+数字+記号(32) | 94 | 79ビット |
| パスフレーズ(7776語のワードリスト) | 7776 | 約51ビット/語 |
参考:
- 50ビット未満:現代のハードウェアで数秒〜数時間でクラック可能
- 50〜70ビット:レート制限があれば低リスクアカウントには十分
- 70ビット以上:ほとんどの用途で強力
- 100ビット以上:将来にわたって安全
強力なパスワードを生成する
ブラウザのcrypto.getRandomValues() APIを使用して暗号論的にランダムなパスワードを生成します。長さ・文字セット・生成数のオプション付き。データはサーバーに送信されません。
パスワードジェネレーターの仕組み
安全なパスワードジェネレーターは**暗号論的安全な疑似乱数生成器(CSPRNG)**を使用する必要があります。標準的なRNGではいけません。
この違いは重要です:
// 誤り:Math.random()は暗号論的に安全ではない
// 攻撃者がシードを知っていれば、全出力を予測できる
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let password = '';
for (let i = 0; i < 16; i++) {
password += chars[Math.floor(Math.random() * chars.length)];
}
// 正しい:crypto.getRandomValues()は暗号論的に安全
function generatePassword(length = 16, charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*') {
const array = new Uint32Array(length);
crypto.getRandomValues(array);
return Array.from(array, n => charset[n % charset.length]).join('');
}
Pythonの場合:
import secrets
import string
def generate_password(length=16, use_symbols=True):
alphabet = string.ascii_letters + string.digits
if use_symbols:
alphabet += string.punctuation
return ''.join(secrets.choice(alphabet) for _ in range(length))
# secretsモジュールはos.urandom()を使用 — 暗号論的に安全
password = generate_password(20)
注意:Pythonのrandom.choice()は暗号論的に安全ではありません。セキュリティが重要なランダム性には常にsecrets.choice()を使用してください。
パスフレーズ:ランダム文字列の代替
パスフレーズはランダムな辞書の単語を並べたものです:
correct-horse-battery-staple
violet-mountain-cloud-seven-river
7776語のリスト(Diceware)から各単語を選ぶと、約12.9ビットのエントロピーが加わります。4語で約52ビット、5語で約64ビット、6語で約77ビットです。
メリット:
- モバイルでの入力が楽
- 時折覚える必要がある場合に記憶しやすい
- 視覚的に区別しやすい
デメリット:
- 長い(より多くの文字を入力する必要がある)
- 長いパスフレーズを壊す文字数制限があるシステムも存在する
長さと複雑さのどちらが大切か
長くするか複雑にするかを選択しなければならない場合、長さを選んでください。1文字追加するごとに探索空間はプールサイズ(26〜94倍)だけ増加します。すでに小文字・大文字・数字を使っている場合に特殊記号を追加しても、16文字のパスワードでは3ビットしか増えません。
実践的な推奨:16文字以上の混合文字クラスを使用してください。
パスワードを記憶しようとしないこと
アカウントごとにユニークなランダムパスワードを生成していれば(そうすべきです)、それらを暗記することはできません。これがパスワードマネージャーが存在する理由です。
パスワードマネージャーは、すべてのパスワードを1つのマスターパスワードで暗号化して保存します。強力なパスフレーズを1つ覚えれば、後はマネージャーが処理します。
おすすめのツール:
- Bitwarden — オープンソース、無料プランが優秀、セルフホスト可能
- 1Password — 洗練されたUX、チーム共有機能が充実
- KeePassXC — ローカルのみ、クラウド依存なし
パスワードの使い回しは絶対にしないでください。1つのデータ侵害で、使い回したすべてのアカウントが露出します。
開発者向け:シークレットの生成
アプリケーションシークレット(APIキー・JWTシークレット・セッションキー・暗号化キー)には、プラットフォームの安全なランダムバイト関数を使用してください:
# シェル — 32ランダムバイトを16進数で(ほとんどのシークレットに適切)
openssl rand -hex 32
# シェル — base64エンコード
openssl rand -base64 32
import secrets
# 32バイトの16進数シークレット(64文字)
api_key = secrets.token_hex(32)
# URLセーフなbase64シークレット
session_secret = secrets.token_urlsafe(32)
// Node.js
import { randomBytes } from 'crypto';
const apiKey = randomBytes(32).toString('hex');
JWTシークレットには少なくとも256ビット(32バイト)を使用してください。AES-256には正確に32バイトが必要です。人間が記憶できる文字列を暗号鍵として使用してはいけません。
Have I Been Pwned との連携
Have I Been Pwned(HIBP)APIを使うと、実際のパスワードを送信せずに、パスワードが既知のデータ侵害に登場しているかチェックできます:
- パスワードのSHA-1ハッシュを計算
- ハッシュの最初の5文字だけをAPIに送信
- APIはその5文字で始まるすべてのハッシュを返す
- フルハッシュがリストにあるかをローカルで確認
import hashlib
import requests
def is_pwned(password):
sha1 = hashlib.sha1(password.encode()).hexdigest().upper()
prefix, suffix = sha1[:5], sha1[5:]
response = requests.get(f'https://api.pwnedpasswords.com/range/{prefix}')
hashes = (line.split(':') for line in response.text.splitlines())
return any(h == suffix for h, _ in hashes)
if is_pwned("P@ssw0rd1"):
print("このパスワードは既知の侵害に登場しています!")
HIBPデータベースに登場するパスワードは、見た目の複雑さにかかわらず絶対に使用しないでください。
まとめ
| 目標 | 推奨事項 |
|---|---|
| ランダムパスワード | 16文字以上、混合文字セット |
| 記憶可能なパスフレーズ | Dicewareから6語以上 |
| アプリケーションシークレット | secrets.token_hex(32) または openssl rand -hex 32 |
| パスワード保存 | Argon2id(3回以上のイテレーション、64MBメモリ) |
| 侵害チェック | Have I Been Pwned API |
| 日常のパスワード管理 | Bitwarden または 1Password |
エントロピー・ランダム性・一意性 — 恣意的な複雑さルールではなく、これらこそがパスワードを安全にするものです。