はじめに 「ランダム文字列生成」は“それっぽいIDやトークンを安全に作る”技
業務システムで「ランダム文字列」が欲しくなる場面はたくさんあります。
一時パスワード
メール確認用トークン
招待コード
ファイル名の衝突回避用サフィックス
ここで大事なのは、
「ただの“なんとなくランダム”」ではなく
「用途に応じて“十分に安全で、扱いやすいランダム文字列”」
を作ることです。
ここでは、初心者向けに、
C# でランダム文字列を作る基本パターンRandom を使う場合と、セキュアな乱数(RandomNumberGenerator)を使う場合の違い
文字種(英大文字・小文字・数字など)の設計
業務ユーティリティとしてのまとめ方
を、例題付きでかみ砕いて説明していきます。
ランダム文字列生成の基本の考え方
「やりたいこと」を分解するとこうなる
ランダム文字列を作る処理は、分解するととてもシンプルです。
1文字ずつ、「使ってよい文字の集合」からランダムに1文字選ぶ
それを指定された長さ分だけ繰り返す
最後に文字列として返す
つまり、
「どの文字を使ってよいか(文字セット)」
「どうやってランダムなインデックスを選ぶか(乱数生成)」
この2つを決めれば、あとはループで組み立てるだけです。
まずは「英数字だけ」のランダム文字列を目標にする
最初のゴールとして、
「英大文字・英小文字・数字からなるランダム文字列」
を作れるようになると、かなり多くの用途をカバーできます。
例:"aZ3kP9"、"X7f2Q0bC" など。
基本版:Random を使ったランダム文字列生成
文字セットを決める
まず、「使ってよい文字」を1つの文字列として定義します。
using System;
using System.Text;
public static class RandomStringUtil
{
private const string DefaultChars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" +
"0123456789";
public static string Generate(int length)
{
if (length <= 0)
{
throw new ArgumentOutOfRangeException(nameof(length), "length は 1 以上にしてください。");
}
var random = new Random();
var sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
{
int index = random.Next(DefaultChars.Length);
char c = DefaultChars[index];
sb.Append(c);
}
return sb.ToString();
}
}
C#ここでやっていることを言葉で追うと、
DefaultChars に「使ってよい文字」を全部並べておくRandom.Next(DefaultChars.Length) で 0〜文字数-1 のランダムなインデックスを得る
そのインデックスの文字を取り出して StringBuilder に追加
これを length 回繰り返す
という流れです。
動作例
Console.WriteLine(RandomStringUtil.Generate(8)); // 例: aZ3kP9qR
Console.WriteLine(RandomStringUtil.Generate(16)); // 例: X7f2Q0bC9mLp3HsT
C#毎回違う文字列が生成されます(もちろん、たまたま同じになる可能性はゼロではありませんが、実務的には十分ランダムです)。
Random の“落とし穴”と、実務での注意点
new Random() を毎回 new する問題
上のコードは「サンプルとして」は分かりやすいのですが、
実務でそのまま使うと、1つ問題があります。
new Random() は、デフォルトでは「現在時刻」を種にします。
短時間に何度も new Random() すると、同じ種から同じ乱数列が出てしまうことがあります。
例えば、ループの中で毎回 new Random() していると、
「全部同じランダム文字列ができた」なんてことが起こりえます。
対策:Random を使い回す
簡単な対策は、「Random を static フィールドとして1つだけ持ち回す」ことです。
public static class RandomStringUtil
{
private static readonly Random _random = new Random();
private const string DefaultChars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" +
"0123456789";
public static string Generate(int length)
{
if (length <= 0)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
var sb = new StringBuilder(length);
lock (_random) // マルチスレッド環境を考えるならロックしておく
{
for (int i = 0; i < length; i++)
{
int index = _random.Next(DefaultChars.Length);
sb.Append(DefaultChars[index]);
}
}
return sb.ToString();
}
}
C#これで、「短時間に大量に呼ばれても、同じ種から同じ列が繰り返される」問題を避けられます。
ただし、セキュリティ用途(パスワード・トークンなど)には Random は不十分です。
そこは次のセクションで扱います。
セキュア版:RandomNumberGenerator を使ったランダム文字列生成
なぜセキュアな乱数が必要か
一時パスワード
認証トークン
パスワードリセット用URLのキー
のような「攻撃者に推測されたくない」用途では、Random ではなく 暗号論的擬似乱数 を使う必要があります。
C# では、System.Security.Cryptography.RandomNumberGenerator(.NET 6 以降なら RandomNumberGenerator.GetInt32)がそれにあたります。
RandomNumberGenerator.GetInt32 を使う実装
using System;
using System.Security.Cryptography;
using System.Text;
public static class SecureRandomStringUtil
{
private const string DefaultChars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" +
"0123456789";
public static string Generate(int length)
{
if (length <= 0)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
var sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
{
int index = RandomNumberGenerator.GetInt32(DefaultChars.Length);
sb.Append(DefaultChars[index]);
}
return sb.ToString();
}
}
C#RandomNumberGenerator.GetInt32(maxValue) は、
0 以上 maxValue 未満の整数を、暗号論的に安全な乱数として返してくれます。
動作例
Console.WriteLine(SecureRandomStringUtil.Generate(32));
// 例: aZ3kP9qRX7f2Q0bC9mLp3HsT1uVwYz0K
C#見た目は普通のランダム文字列ですが、
内部では「セキュアな乱数」を使っているので、
攻撃者がパターンを推測するのは非常に困難です。
文字セットを切り替えられるようにする
英数字だけ/数字だけ/大文字だけ…を選べるように
用途によっては、
数字だけのワンタイムコード(例:6桁の数字)
英大文字だけの招待コード
記号も含めた強めのパスワード
など、使いたい文字種が変わります。
そこで、「文字セットを引数で渡せる」ようにしておくと便利です。
public static class SecureRandomStringUtil
{
public const string CharsUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public const string CharsLower = "abcdefghijklmnopqrstuvwxyz";
public const string CharsDigit = "0123456789";
public static string Generate(int length, string allowedChars)
{
if (length <= 0)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
if (string.IsNullOrEmpty(allowedChars))
{
throw new ArgumentException("allowedChars は 1 文字以上必要です。", nameof(allowedChars));
}
var sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
{
int index = RandomNumberGenerator.GetInt32(allowedChars.Length);
sb.Append(allowedChars[index]);
}
return sb.ToString();
}
public static string GenerateAlphaNumeric(int length)
=> Generate(length, CharsUpper + CharsLower + CharsDigit);
public static string GenerateDigits(int length)
=> Generate(length, CharsDigit);
}
C#動作例
Console.WriteLine(SecureRandomStringUtil.GenerateAlphaNumeric(12));
// 例: aZ3kP9qRX7f2
Console.WriteLine(SecureRandomStringUtil.GenerateDigits(6));
// 例: 493027
Console.WriteLine(SecureRandomStringUtil.Generate(8, SecureRandomStringUtil.CharsUpper));
// 例: QZMTLPRK
C#こうしておくと、「どんな文字を使ってよいか」を呼び出し側で柔軟に決められます。
実務での設計ポイント
用途ごとに「どのユーティリティを使うか」を決める
ざっくり分けると、こんな感じの使い分けが現実的です。
ログの相関ID、テスト用のダミーIDなど
→ Random ベースでも十分(ただし Random の使い回しは意識する)
一時パスワード、認証トークン、URLキーなど
→ RandomNumberGenerator ベースのセキュア版を必ず使う
そして、それをコード上で明確にするために、
RandomStringUtil(非セキュア・軽量)SecureRandomStringUtil(セキュア用途)
のようにクラスを分けておくと、
「間違ってセキュア用途に Random を使ってしまう」事故を防ぎやすくなります。
長さと文字種は“人間の使い勝手”も考える
例えば、一時パスワードをユーザーにメールで送り、
それを手入力してもらう場合、
紛らわしい文字(0 と O、1 と l など)を避ける
長さは 6〜8 文字程度にして、入力負荷を下げる
といった工夫も大事です。
文字セットを自分で定義できるようにしておけば、
こうした「人間側の事情」も簡単に反映できます。
まとめ 「ランダム文字列生成ユーティリティ」は“用途に合わせて乱数の強さと文字種を選ぶ道具”
ランダム文字列生成は、一見シンプルですが、
どの乱数(Random / RandomNumberGenerator)を使うか
どの文字を使ってよいか(文字セット)
どのくらいの長さにするか
といった設計次第で、「安全性」と「使いやすさ」が大きく変わります。
押さえておきたいポイントは、
基本の形は「文字セットからランダムに1文字ずつ選んで連結」Random はテストや軽い用途向け、セキュリティ用途には不十分
セキュアな用途では RandomNumberGenerator.GetInt32 を使う
文字セットを引数にして、英数字・数字だけ・大文字だけなどを切り替えられるようにする
用途ごとにユーティリティを分けておくと、後から読んだときに意図が分かりやすい
ここまで理解できれば、「なんとなくランダムっぽい文字列を作っている」段階から一歩進んで、
“業務要件とセキュリティを両立したランダム文字列ユーティリティ”を、自分の手で設計・実装できるようになっていきます。
