JavaScript Tips | 文字列ユーティリティ:生成 - パスワード生成

JavaScript JavaScript
スポンサーリンク

まず「パスワード生成」で絶対に外せない前提

ランダム文字列と違って、パスワード生成は最初から「セキュリティ前提」で考える必要があります。

ここを曖昧にすると、一見それっぽく動くけど「簡単に総当たりされるパスワード」を量産してしまいます。

押さえておきたい前提はこの3つです。

  1. Math.random() は使わない(暗号的に安全ではない)。
  2. crypto.getRandomValues を使って「OS 由来の安全な乱数」を使う。
  3. 文字種(大文字・小文字・数字・記号)と長さのルールを、ユーティリティ側でしっかり決める。

この3つを守るだけで、「なんとなくそれっぽい」から「実務で使える」レベルに一気に上がります。


パスワードに使う「文字セット」を設計する

よくある要件を文字セットに落とし込む

例えば、こんな要件をよく見ます。

英大文字・英小文字・数字・記号を含めたい
紛らわしい文字(0O1l)は避けたい
長さは 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. まず、各カテゴリから1文字ずつ必ず選ぶ。
  2. 残りの文字は全カテゴリを混ぜた文字セットからランダムに選ぶ。
  3. 最後に、全体をシャッフルして「先頭に偏らないようにする」。

コードにするとこうなります。

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つ以上含みます。


実務での使いどころと注意点

ユーザーに見せるパスワード/招待コード

ユーザーに「初期パスワード」や「招待コード」を見せる場合は、
強度だけでなく「読みやすさ」も大事になります。

紛らわしい文字(0O1l)を避ける
記号の種類を少し減らして、入力しやすくする

などは、ユーザー体験としてかなり効きます。

その意味で、さきほどの「紛らわしい文字を除いた文字セット」は、
実務でかなりちょうどいい落としどころです。

「保存方法」はまた別の話

ここで作っているのは「パスワードの文字列そのもの」です。
実際のシステムでは、これをそのままDBに保存してはいけません。

保存するときは必ずハッシュ化(bcrypt など)する必要がありますが、
それは「パスワード生成」とは別レイヤーの話です。

ユーティリティの責務は「強度のあるパスワード文字列を作ること」まで。
保存方法は認証・セキュリティの設計側で扱う、という分担を意識しておくと整理しやすくなります。


ちょっとだけ手を動かしてみる

コンソールで、次の順番で試してみてください。

generatePassword(12);
generatePassword(12);

generateStrongPassword(12);
generateStrongPassword(16);
JavaScript

出てきた文字列を眺めながら、

大文字・小文字・数字・記号がちゃんと混ざっているか
紛らわしい文字が含まれていないか
長さを変えるとどんな印象になるか

を、自分の目で確認してみてください。

そのうえで、自分のプロジェクトに

export function generatePassword(length?) { ... }
export function generateStrongPassword(length?) { ... }
JavaScript

の2本を置いて、

「テスト用や一時用途 → generatePassword」
「ユーザーに配る/本番用 → generateStrongPassword」

という使い分けをルール化してみてください。

それができた瞬間、あなたの「パスワード生成」は
なんとなくのランダム文字列から、
目的と強度を意識した“業務レベルのユーティリティ”に変わります。

タイトルとURLをコピーしました