まず「パスワード生成」で絶対に外せない前提
ランダム文字列と違って、パスワード生成は最初から「セキュリティ前提」で考える必要があります。
ここを曖昧にすると、一見それっぽく動くけど「簡単に総当たりされるパスワード」を量産してしまいます。
押さえておきたい前提はこの3つです。
Math.random()は使わない(暗号的に安全ではない)。crypto.getRandomValuesを使って「OS 由来の安全な乱数」を使う。- 文字種(大文字・小文字・数字・記号)と長さのルールを、ユーティリティ側でしっかり決める。
この3つを守るだけで、「なんとなくそれっぽい」から「実務で使える」レベルに一気に上がります。
パスワードに使う「文字セット」を設計する
よくある要件を文字セットに落とし込む
例えば、こんな要件をよく見ます。
英大文字・英小文字・数字・記号を含めたい
紛らわしい文字(0 と O、1 と l)は避けたい
長さは 12〜16 文字くらいにしたい
これをそのままコードに落とすと、まず「使ってよい文字の集合」を定義することになります。
const UPPER = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // I, O を除外
const LOWER = "abcdefghijkmnpqrstuvwxyz"; // l, o を除外
const DIGITS = "23456789"; // 0,1 を除外
const SYMBOLS = "!@#$%^&*";
JavaScriptそして、これらを結合して「パスワード候補の文字セット」を作ります。
const PASSWORD_CHARS = UPPER + LOWER + DIGITS + SYMBOLS;
JavaScriptここまで決めておくと、「どんなパスワードが生成されるか」がとてもイメージしやすくなります。
セキュアな乱数でパスワードを生成する
crypto.getRandomValues を使った基本形
ランダム文字列のときと同じく、パスワード生成では必ず crypto.getRandomValues を使います。
function secureRandomIndex(max) {
const array = new Uint32Array(1);
crypto.getRandomValues(array);
return array[0] % max;
}
JavaScriptこの小さな関数は、「0〜max-1 の範囲のランダムな整数」を安全に作るためのものです。
これを使って、パスワード生成関数を書きます。
function generatePassword(length = 12) {
if (length <= 0) return "";
const chars = PASSWORD_CHARS;
let result = "";
for (let i = 0; i < length; i++) {
const index = secureRandomIndex(chars.length);
result += chars[index];
}
return result;
}
JavaScriptこれで、指定した長さの「それなりに強い」パスワードが生成できます。
generatePassword(12); // 例: "aF7!kP2#qM8@"
generatePassword(16); // 例: "G$8mNp2@qR5!sT9"
JavaScriptここまでで、「安全な乱数を使って、決めた文字セットからパスワードを作る」という基本形ができました。
重要ポイント:必ず「各文字種を最低1つ含める」ようにする
なぜ「文字種のバランス」が大事か
今の実装だと、「たまたま記号が1つも入らないパスワード」が生成される可能性があります。
それでもそこそこ強いですが、よくあるポリシーはこうです。
英大文字を1文字以上含む
英小文字を1文字以上含む
数字を1文字以上含む
記号を1文字以上含む
このルールを満たすように生成しておくと、
「生成したパスワードが自分のパスワードポリシーに引っかかる」という悲しい事故を防げます。
各カテゴリから最低1文字ずつ取る実装
考え方はシンプルです。
- まず、各カテゴリから1文字ずつ必ず選ぶ。
- 残りの文字は全カテゴリを混ぜた文字セットからランダムに選ぶ。
- 最後に、全体をシャッフルして「先頭に偏らないようにする」。
コードにするとこうなります。
function shuffleString(str) {
const array = str.split("");
for (let i = array.length - 1; i > 0; i--) {
const j = secureRandomIndex(i + 1);
[array[i], array[j]] = [array[j], array[i]];
}
return array.join("");
}
function generateStrongPassword(length = 12) {
if (length < 4) {
throw new Error("length must be at least 4");
}
let password = "";
// 各カテゴリから最低1文字
password += UPPER[secureRandomIndex(UPPER.length)];
password += LOWER[secureRandomIndex(LOWER.length)];
password += DIGITS[secureRandomIndex(DIGITS.length)];
password += SYMBOLS[secureRandomIndex(SYMBOLS.length)];
const chars = PASSWORD_CHARS;
// 残りを埋める
for (let i = password.length; i < length; i++) {
password += chars[secureRandomIndex(chars.length)];
}
// 先頭に偏らないようにシャッフル
return shuffleString(password);
}
JavaScriptこれで、例えばこうなります。
generateStrongPassword(12); // 例: "f7$Qm2@Kp8#s"
JavaScriptこのパスワードは必ず「大文字・小文字・数字・記号」を1つ以上含みます。
実務での使いどころと注意点
ユーザーに見せるパスワード/招待コード
ユーザーに「初期パスワード」や「招待コード」を見せる場合は、
強度だけでなく「読みやすさ」も大事になります。
紛らわしい文字(0 と O、1 と l)を避ける
記号の種類を少し減らして、入力しやすくする
などは、ユーザー体験としてかなり効きます。
その意味で、さきほどの「紛らわしい文字を除いた文字セット」は、
実務でかなりちょうどいい落としどころです。
「保存方法」はまた別の話
ここで作っているのは「パスワードの文字列そのもの」です。
実際のシステムでは、これをそのままDBに保存してはいけません。
保存するときは必ずハッシュ化(bcrypt など)する必要がありますが、
それは「パスワード生成」とは別レイヤーの話です。
ユーティリティの責務は「強度のあるパスワード文字列を作ること」まで。
保存方法は認証・セキュリティの設計側で扱う、という分担を意識しておくと整理しやすくなります。
ちょっとだけ手を動かしてみる
コンソールで、次の順番で試してみてください。
generatePassword(12);
generatePassword(12);
generateStrongPassword(12);
generateStrongPassword(16);
JavaScript出てきた文字列を眺めながら、
大文字・小文字・数字・記号がちゃんと混ざっているか
紛らわしい文字が含まれていないか
長さを変えるとどんな印象になるか
を、自分の目で確認してみてください。
そのうえで、自分のプロジェクトに
export function generatePassword(length?) { ... }
export function generateStrongPassword(length?) { ... }
JavaScriptの2本を置いて、
「テスト用や一時用途 → generatePassword」
「ユーザーに配る/本番用 → generateStrongPassword」
という使い分けをルール化してみてください。
それができた瞬間、あなたの「パスワード生成」は
なんとなくのランダム文字列から、
目的と強度を意識した“業務レベルのユーティリティ”に変わります。
