Java Tips | 基本ユーティリティ:ランダム文字列生成

Java Java
スポンサーリンク

ランダム文字列生成は「安全なカギ札を作る作業」

業務システムでは、一時パスワード、認証コード、トークン、セッションID、テストデータ用のダミー値など、「誰ともかぶらず、予測されにくい文字列」が欲しくなる場面がたくさん出てきます。
このときに使うのが「ランダム文字列生成」です。

ただし、なんとなく Random を使って適当に文字を並べるだけだと、「推測されやすい」「セキュリティ的に弱い」「用途に合っていない」といった問題が起きます。
ここでは、初心者向けに「まず押さえるべき基本」と「業務で本当に使えるユーティリティの形」を、例題付きで丁寧に整理していきます。


まずは基本:英数字ランダム文字列の作り方

Random を使った一番シンプルな実装

「とりあえず英数字のランダム文字列が欲しい」という一番基本的なパターンから見ていきます。

import java.util.Random;

public class RandomStrings {

    private static final String CHARSET =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    public static String randomString(int length) {
        Random random = new Random();
        StringBuilder sb = new StringBuilder(length);

        for (int i = 0; i < length; i++) {
            int index = random.nextInt(CHARSET.length());
            sb.append(CHARSET.charAt(index));
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        System.out.println(randomString(16));  // 例: "aZ3k9Pq1Xy0B7cD2"
    }
}
Java

やっていることはとてもシンプルで、「使ってよい文字の一覧(CHARSET)を用意し、その中からランダムに 1 文字ずつ選んで連結する」というだけです。
このパターンは、テストデータ生成や「そこまでセキュリティが重要でない一時的な識別子」など、ライトな用途なら十分使えます。

ただし、Random はあくまで一般用途向けの疑似乱数であり、「セキュリティ的に強い乱数」ではありません。
一時パスワードや認証トークンのように「攻撃者に予測されたくない」用途では、次に説明する SecureRandom を使う必要があります。


セキュリティが絡むなら SecureRandom 一択

SecureRandom が必要になる理由

java.security.SecureRandom は、「暗号用途にも耐えられる、予測されにくい乱数」を生成するためのクラスです。
普通の Random は内部状態が比較的単純で、十分な観測があれば将来の値を推測される可能性がありますが、SecureRandom はそのリスクを下げるように設計されています。

パスワード、トークン、セッションID、認証コードなど、「推測されると困るもの」を作るときは、必ず SecureRandom を使う、と覚えておいてください。

SecureRandom を使った安全なランダム文字列

先ほどの実装を、SecureRandom ベースに書き換えてみます。

import java.security.SecureRandom;

public class SecureRandomStrings {

    private static final String CHARSET =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    private static final SecureRandom SECURE_RANDOM = new SecureRandom();

    public static String secureRandomString(int length) {
        StringBuilder sb = new StringBuilder(length);

        for (int i = 0; i < length; i++) {
            int index = SECURE_RANDOM.nextInt(CHARSET.length());
            sb.append(CHARSET.charAt(index));
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        System.out.println(secureRandomString(32));  // 例: "k3F9sP0xYz1Qw8ErT6uVbN4mC2jH7L0"
    }
}
Java

ここでの重要ポイントは二つです。

一つ目は、「SecureRandom を毎回 new しないで、static フィールドとして 1 個だけ持つ」ことです。
SecureRandom は初期化コストがそれなりに高いので、使い回したほうが効率的です。

二つ目は、「CHARSET を自分で決められる」ということです。
英数字だけにするのか、記号も含めるのか、見間違えやすい文字(O と 0、l と 1 など)を除外するのか、といったポリシーを、用途に合わせて設計できます。


実務で使いやすい「ランダム文字列ユーティリティ」の形

用途別にメソッドを分ける

業務コードでは、「用途ごとに少しずつ要件が違う」ことがよくあります。
例えば、次のようなパターンです。

一時パスワード用:英大小文字+数字+記号、長さ 12 以上。
メール認証コード用:数字だけ、長さ 6。
トークン用:英数字のみ、長さ 32、SecureRandom 必須。

これを毎回バラバラに書くのではなく、ユーティリティクラスにまとめておくと、コードの意図がとても読みやすくなります。

import java.security.SecureRandom;

public final class RandomTokens {

    private static final String ALPHANUMERIC =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    private static final String DIGITS = "0123456789";

    private static final String PASSWORD_CHARS =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";

    private static final SecureRandom SECURE_RANDOM = new SecureRandom();

    private RandomTokens() {}

    public static String authCode6Digits() {
        return randomFromCharset(DIGITS, 6);
    }

    public static String apiToken32() {
        return randomFromCharset(ALPHANUMERIC, 32);
    }

    public static String tempPassword12() {
        return randomFromCharset(PASSWORD_CHARS, 12);
    }

    public static String randomFromCharset(String charset, int length) {
        StringBuilder sb = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            int index = SECURE_RANDOM.nextInt(charset.length());
            sb.append(charset.charAt(index));
        }
        return sb.toString();
    }
}
Java

呼び出し側は、用途に応じてメソッド名だけ見れば意図が分かります。

String code  = RandomTokens.authCode6Digits();   // 認証コード
String token = RandomTokens.apiToken32();        // API トークン
String pass  = RandomTokens.tempPassword12();    // 一時パスワード
Java

ここで深掘りしておきたいのは、「用途ごとにルールを固定しておくと、後から仕様を変えやすい」という点です。
例えば「一時パスワードは記号をやめて英数字だけにしたい」となったとき、PASSWORD_CHARS を変えるだけで済みます。
呼び出し側のコードは一切触らなくてよいので、影響範囲が小さく、レビューもしやすくなります。


UUID を使った「なんちゃってランダム文字列」という選択肢

UUID をそのまま使う簡易パターン

「とにかく一意っぽい文字列が欲しいだけで、英数字だけでなくてもいい」という場合、UUID をそのまま使うのも実務ではよくあります。

import java.util.UUID;

public class UuidBasedRandom {

    public static String uuidString() {
        return UUID.randomUUID().toString();  // 例: "550e8400-e29b-41d4-a716-446655440000"
    }

    public static String uuidCompact() {
        return UUID.randomUUID().toString().replace("-", "");  // 32 桁
    }
}
Java

UUID は内部的に暗号学的に強い乱数を使う実装が多く、「かなり高い確率で一意な文字列」を簡単に得られます。
ただし、形式が固定(16進数+ハイフン)で長さも決まっているので、「桁数を自由に変えたい」「文字種を制御したい」といった用途には向きません。

UUID と SecureRandom の使い分け

ざっくり言うと、次のような感覚で使い分けるとよいです。

「ID として一意性が欲しいだけ」なら UUID。
「パスワードや認証コードなど、形式や長さを細かく制御したい」なら SecureRandom+自前の文字セット。

UUID は「一意性担当」、SecureRandom+文字セットは「形式と安全性担当」とイメージすると、設計の整理がしやすくなります。


ランダム文字列生成で気をつけたいポイント

Random と SecureRandom を混同しない

一番大事なのは、「セキュリティが絡むところで Random を使わない」ということです。
Random はテストデータや単なるシャッフルには便利ですが、攻撃者に観測される可能性がある場面では危険です。

一時パスワード、トークン、セッションID、認証コードなど、「破られると困るもの」は、必ず SecureRandom ベースの実装にしておきましょう。

文字セットと長さは「用途から逆算」する

「とりあえず 8 桁でいいか」と適当に決めるのではなく、「何回試行されたら危ないか」「どれくらいの期間有効か」といった観点から、必要な強度を考えるのが本来の姿です。
初心者のうちは、まずは「一時パスワードなら 12〜16 文字程度」「トークンなら 32 文字以上」といった、少し余裕のある設定にしておくとよいです。

また、ユーザーが手入力するコード(SMS 認証コードなど)は、数字だけ・6 桁程度にしておくと、入力ミスが減って UX が良くなります。
逆に、ユーザーが直接触らないトークンは、多少長くても問題ありません。


まとめ:初心者がランダム文字列生成で身につけるべき感覚

ランダム文字列生成は、「ただランダムに見えればいい」のではなく、「用途に合った安全性と扱いやすさを持った文字列を設計する」作業です。

まずは、RandomSecureRandom の違いを理解し、「セキュリティが絡むところでは SecureRandom 一択」と体に刻む。
次に、「使う文字セット」と「長さ」を用途から逆算し、それをユーティリティクラスに閉じ込めて、呼び出し側はメソッド名だけで意図が分かるようにする。
UUID は「一意な ID が欲しいだけ」の場面での簡易な選択肢として覚えておく。

ここまで押さえれば、「とりあえず適当にランダムっぽい文字列を作る」段階から一歩抜け出せます。

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