【JavaScript】指定した文字数のランダムな文字列を生成する方法|Math.randomとcrypto(パスワード・トークンも安全に)

JavaScriptで「指定した文字数のランダムな文字列」を作りたい場面はよくあります。一時的な要素ID・テスト用ダミーデータのような簡易用途から、パスワード・トークン・CSRF値のようにセキュリティが関わる用途まで様々です。

重要なのは用途による使い分けです。簡易用途なら Math.random() で十分ですが、パスワードやトークンには Math.random() を使ってはいけません(予測可能で危険)。この記事では安全な crypto.getRandomValues() を主役に、出現確率の偏り(modulo bias)の回避まで解説します。

この記事の結論:簡易用途は Math.random()セキュア用途(パスワード・トークン)は crypto.getRandomValues() または crypto.randomUUID() を使います。Math.random() は暗号学的に安全ではないため、機密性のある値には絶対に使わないでください。
スポンサーリンク

Math.random() で生成する(簡易用途のみ)

文字セットからランダムに1文字ずつ選んで連結する、最も基本的な方法です。一時IDやダミーデータなどセキュリティに関係しない用途に向いています。

Math.random() で生成
function randomString(length) {
  const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  let result = "";
  for (let i = 0; i < length; i++) {
    result += chars[Math.floor(Math.random() * chars.length)];
  }
  return result;
}

console.log(randomString(8)); // 例: "7Pb4R9Qc"
パスワード・トークンには使わないでください。Math.random() は高速ですが暗号学的に安全ではなく、生成値が予測される可能性があります。機密性のある値には次の crypto を使います。

crypto.getRandomValues() で安全に生成する(推奨)

パスワードやトークンには、暗号学的に安全な乱数を返す crypto.getRandomValues() を使います。ブラウザ・Node.js(v15+ は globalThis.crypto)の両方で利用できます。

crypto.getRandomValues() で生成
function secureRandomString(length) {
  const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  const values = crypto.getRandomValues(new Uint8Array(length));
  let result = "";
  for (let i = 0; i < length; i++) {
    result += chars[values[i] % chars.length];
  }
  return result;
}

console.log(secureRandomString(16)); // 例: "k8Df2pQzR1mXa9Lb"
上記は実用上ほぼ問題ありませんが、文字セットの長さが256を割り切らない場合は出現確率にわずかな偏り(modulo bias)が生じます。厳密に均一にしたい場合は次の方法を使います。

modulo bias を回避して厳密に均一にする

byte % 文字数 は、文字数が256の約数でないと一部の文字が出やすくなります(例:文字数62なら 256 ÷ 62 = 4 余り 8 で、先頭8文字がわずかに多く出る)。余りの範囲のバイトを捨てる(リジェクションサンプリング)ことで偏りをなくせます。

modulo bias を回避
function unbiasedRandomString(length, chars =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") {
  const result = [];
  // 文字数で割り切れる最大値。これ以上のバイトは捨てて偏りを防ぐ
  const limit = Math.floor(256 / chars.length) * chars.length;

  while (result.length < length) {
    const bytes = crypto.getRandomValues(new Uint8Array(length - result.length));
    for (const byte of bytes) {
      if (byte < limit) {
        result.push(chars[byte % chars.length]);
        if (result.length === length) break;
      }
    }
  }
  return result.join("");
}

console.log(unbiasedRandomString(16));

もっと手軽に:toString(36) と crypto.randomUUID()

細かい制御が不要なら、もっと短く書ける方法もあります。

toString(36) と randomUUID
// 簡易: 0-9 と a-z のランダム文字列(長さは可変・非セキュア)
const id = Math.random().toString(36).slice(2, 10);
console.log(id); // 例: "f3k9zq1p"

// 一意なID(UUID v4・暗号学的に安全)
const uuid = crypto.randomUUID();
console.log(uuid); // 例: "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"

// セキュアな16進トークン(32文字)
const bytes = crypto.getRandomValues(new Uint8Array(16));
const token = [...bytes].map(b => b.toString(16).padStart(2, "0")).join("");
console.log(token); // 例: "3f8a1c9e..."(32桁の16進)
Math.random().toString(36) は手軽ですが非セキュアで長さも一定になりません(末尾の0が落ちる等)。トークンには crypto.randomUUID()getRandomValues +16進化を使ってください。

文字セットを使い分ける

用途に応じて文字セットを変えれば、英字のみ・数字のみ・記号入りなどを生成できます。

文字セットの例
const SETS = {
  alphanumeric: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
  alpha:        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
  numeric:      "0123456789",
  // パスワード向け(紛らわしい 0/O/1/l/I を除外+記号)
  password:     "abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*",
};

// 上の unbiasedRandomString に文字セットを渡す
console.log(unbiasedRandomString(12, SETS.numeric));  // 例: "830492716508"
console.log(unbiasedRandomString(16, SETS.password)); // 例: "k8Df%pQz@1mXa9Lb"

Math.random と crypto の使い分け

項目 Math.random() crypto.getRandomValues()
安全性 非セキュア(予測可能) 暗号学的に安全
速度 非常に高速 高速
向いている用途 一時ID・ダミー・演出 パスワード・トークン・CSRF
偏り対策 不要(簡易用途) modulo bias に注意
判断基準はシンプルです:その値が漏れたり予測されたりして困るなら必ず crypto、困らない演出・一時用途だけ Math.random()。迷ったら crypto を使えば安全です。

実用例:パスワードとトークンの生成

パスワード/トークン生成
// 16文字の安全なパスワード(記号入り・紛らわしい文字を除外)
const password = unbiasedRandomString(16, SETS.password);
console.log(password);

// APIトークン(UUID)
const apiToken = crypto.randomUUID();

// セッショントークン(32桁の16進)
const sessionToken = [...crypto.getRandomValues(new Uint8Array(16))]
  .map(b => b.toString(16).padStart(2, "0"))
  .join("");
乱数そのものの生成(数値の乱数・範囲指定)については乱数を生成する方法もあわせて参照してください。

よくある質問(FAQ)

Qランダムな文字列を生成する一番簡単な方法は?
A簡易用途なら Math.random().toString(36).slice(2, 10) が手軽です。ただし非セキュアで長さが一定にならないため、文字数をきっちり指定したい・安全性が必要な場合はcrypto.getRandomValues() を使った関数(記事内の secureRandomString)を使ってください。
Qパスワード生成に Math.random() を使ってはいけないのはなぜ?
AMath.random()暗号学的に安全な乱数ではなく、内部状態から次の値が予測される可能性があるためです。パスワード・トークン・CSRF値などには、暗号学的に安全な crypto.getRandomValues()crypto.randomUUID() を使ってください。
Qmodulo bias とは何ですか?
Abyte % 文字数 で文字を選ぶとき、文字数が256の約数でないと一部の文字がわずかに出やすくなる偏りのことです。厳密に均一にするには、割り切れる範囲を超えたバイトを捨てて引き直す(リジェクションサンプリング)と回避できます。
Q一意なIDが欲しいだけなら何を使えばいいですか?
Acrypto.randomUUID() が最適です。標準化された UUID v4 を1行で生成でき、暗号学的に安全です。古い環境で使えない場合のみ getRandomValues での自前生成を検討します。

まとめ

JavaScriptで指定文字数のランダム文字列を生成する方法のポイントを整理します。

  • 簡易用途(一時ID・演出)は Math.random() で文字セットから選ぶ
  • パスワード・トークンは必ず crypto.getRandomValues() / crypto.randomUUID()
  • 厳密に均一にするなら modulo bias をリジェクションサンプリングで回避
  • 一意ID は crypto.randomUUID()、16進トークンは getRandomValuestoString(16)
  • 文字セットを変えれば英字のみ・数字のみ・記号入りも生成できる

あわせて乱数を生成する方法も確認すると、数値・文字列の両方のランダム生成を扱えるようになります。