Java | Java 標準ライブラリ:SecureRandom

Java Java
スポンサーリンク

SecureRandom は「セキュリティ用の本気の乱数生成器」

java.security.SecureRandom は、一言でいうと

「パスワード・トークン・認証コードなど、“セキュリティが絡む場面専用”の乱数生成器」

です。

前回の Random は、「ゲームのダメージ」「ガチャ」「テストデータ」など
“それっぽくランダムなら十分” な用途向けでした。

でも、パスワードリセット用 URL、ログイン用ワンタイムコード、
API トークン、セッション ID のようなものは、

「攻撃者に予測されない」「解析されにくい」

ことが命です。

Random は「疑似乱数」であり、シードさえ分かれば未来が読めます。
セキュリティ用途には弱すぎる。
そこで登場するのが、より強い乱数源を使う SecureRandom です。


Random と SecureRandom の違いを感覚で押さえる

Random は「予測可能」、SecureRandom は「予測されにくい」

Random は、数学的には「線形合同法」などの簡易なアルゴリズムで乱数を出しています。
シード(初期値)とアルゴリズムが分かれば、
次に出てくる乱数の列は完全に再現できます。

ゲーム・シミュレーション・テストなどにはこれで十分ですが、
攻撃者が「乱数の出方を解析して次のトークンを推測する」という攻撃に弱いです。

SecureRandom は、OS や暗号用の乱数源を使って
「予測が極めて困難な値」を生成します。

内部で、マウスの動き、ネットワークタイミング、CPU ノイズなど、
様々な“予測しづらい情報”を元にしています(プラットフォーム依存ですがイメージとして)。

イメージで言うと、

Random → 乱数っぽく見えれば十分な「おもちゃの乱数」
SecureRandom → 本気で攻撃を意識した「セキュリティ用乱数」

という役割分担です。


基本の使い方:SecureRandom でランダムな数値を作る

インスタンス生成と nextInt の使い方

一番シンプルな使い方はこうです。

import java.security.SecureRandom;

public class SecureRandomSample {
    public static void main(String[] args) {
        SecureRandom secureRandom = new SecureRandom();

        int r1 = secureRandom.nextInt();      // int の乱数
        int r2 = secureRandom.nextInt(100);  // 0〜99 の乱数
        boolean b = secureRandom.nextBoolean();

        System.out.println(r1);
        System.out.println(r2);
        System.out.println(b);
    }
}
Java

メソッド名は Random とほぼ同じです。
nextInt(int bound) の意味も同じで、「0 以上 bound 未満」の整数です。

だから使い方としては、

「Random のところを SecureRandom に差し替える」

という感覚でだいたい合っています。

乱数インスタンスは1つを使い回す

Random と同じく、SecureRandom

「1つインスタンスを作って、それを使い続ける」

のが基本です。

public class TokenGenerator {
    private final SecureRandom random = new SecureRandom();

    public int generateCode() {
        return random.nextInt(1_000_000);  // 0〜999999 の6桁コード候補
    }
}
Java

毎回 new SecureRandom() しても動きますが、
内部的な初期化コストが高くなるので、通常はフィールドとして1つ持ちます。


セキュアなトークンやコードを作る典型パターン

6桁のワンタイムコードを作る

例えば、ログイン認証に使う 6 桁の数字コードを生成してみます。

import java.security.SecureRandom;

public class OneTimeCodeGenerator {
    private final SecureRandom random = new SecureRandom();

    public String generate6DigitCode() {
        int value = random.nextInt(1_000_000); // 0〜999999
        return String.format("%06d", value);   // ゼロ埋めして6桁にする
    }

    public static void main(String[] args) {
        OneTimeCodeGenerator generator = new OneTimeCodeGenerator();
        System.out.println(generator.generate6DigitCode());
    }
}
Java

ここで注意したいのは、
nextInt(1_000_000) が返すのは 0〜999999 の整数なので、
000123 のような先頭ゼロ付きコードもありうることです。

String.format("%06d", value) のようにゼロ埋めしないと、
「6桁のはずなのに長さがバラバラ」という状態になります。

英数字のランダムトークン(URL など)を作る

例えば、パスワードリセット URL に埋め込むトークンなど。

import java.security.SecureRandom;

public class TokenGenerator {
    private static final String CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    private final SecureRandom random = new SecureRandom();

    public String generateToken(int length) {
        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) {
        TokenGenerator generator = new TokenGenerator();
        System.out.println(generator.generateToken(32));  // 32 文字のトークン
    }
}
Java

ここでも Random ではなく SecureRandom を使うことで、

「攻撃者が次のトークンを予測しづらい」

状態を作れます。


シード管理の違い:SecureRandom では「自分で seed を決めない」

Random と違って、明示的に seed を固定しない

Random では、テストのために

Random random = new Random(12345L);  // シードを固定
Java

のように書いて、同じ乱数列を再現することがよくあります。

SecureRandom に対して同じことをやるのは、
セキュリティ的な意味ではあまりよくありません。

もちろん API 上は

SecureRandom random = new SecureRandom();
random.setSeed(12345L);
Java

のようなこともできますが、
通常のセキュリティ用途では、ライブラリに任せて

「OS が提供する強い乱数源から、勝手にシードを初期化してもらう」

ほうが安全です。

テストだけのために「弱い乱数」に変えてしまうのは本末転倒なので、
セキュリティが関わるロジックでは、

テスト側を工夫する
実際の乱数値ではなく、長さやフォーマットだけを確認する

といったアプローチの方が健全です。


SecureRandom をどこで使うべきか/使うべきでないか

使うべきところ(セキュリティ関連)

次のようなものは、原則として SecureRandom を使うべきです。

パスワードリセット用トークン
API トークン、アクセストークン
セッション ID
認証用ワンタイムコード
暗号鍵、IV(初期ベクトル)の生成

こういった値は、攻撃者が推測できてしまうと致命的です。
「Random で何となく乱数を振ってる」状態は、ほぼセキュリティホールと考えていいです。

使わなくてよいところ(非セキュリティ用途)

逆に、

ゲームのダメージ
ガチャの結果
テストデータの仮ID
簡単なシャッフル

などは、Random で十分です。

SecureRandom は通常 Random より重い(遅い)ので、
意味もなく全部を SecureRandom にすると、無駄にコストがかかります。

「攻撃者に読まれたら困るか?」
「推測されたら致命的か?」

を自分に問いかけてみて、
Yes なら SecureRandom、No なら Random や Math.random()、と分けていく感覚です。


よくある誤解・やりがちなミス

「Random だけど十分ランダムに見えるからこれでいい」は危険

見た目がランダムかどうかは問題ではありません。
攻撃者は、「過去に出てきた値からパターンを解析して次を予測する」ことを狙います。

Random はアルゴリズムが単純なので、
知識のある相手からすると「予測対象」になり得ます。

「自分の頭で“たぶん安全”と判断する」のではなく、
「セキュリティ用途なら SecureRandom」という既存の鉄板ルールに乗る方が確実です。

乱数値を短くしすぎる

SecureRandom を使っていても、
例えば「1〜100 のうちから1つだけ」といった小さな空間だと、
総当たり(全部試す)で簡単に破られます。

特にトークンやコードは、

長さ(ビット数)が十分か
有効期限が短く設定されているか

もセットで考える必要があります。

「SecureRandom を使えば何でも絶対安全」ではなく、
「強い乱数源を使うのはセキュリティ設計の一部」と捉えてください。


まとめ:SecureRandom を初心者の頭にどう置いておくか

最後に、イメージを固定します。

SecureRandom は、「セキュリティ用途のための強い乱数生成器」。
Random と API はよく似ているが、内部でより安全な乱数源を使っている。
パスワード・トークン・認証コード・セッションIDなど、攻撃者に予測されたくないものには必須。
ゲームや単なるランダム表示には不要で、Random で十分。

特に意識しておきたいのは、

「セキュリティ寄りの機能を書いているときに、new Random() と書いていないか?」

を必ず自分でチェックすることです。

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