技術開発日記

技術やら日々思ったことを綴ってます。

ランダム文字列生成

先日CodeIQでランダムパスワードを生成するプログラムの問題(挑戦者求む!【ウチに来ない?】[Java]ランダムパスワードの作成 by CodeIQ運営事務局 アプリケーションエンジニアを募集する企業│CodeIQ)
で解答した際に使ったランダム文字列生成のメモです。

そもそもランダム文字列を作るにはいくつかの方法があると思いますが、
僕が思いついたのは以下の5つでした。

  • UUID
  • java.util.Random
  • java.lang.Math#Random
  • RandomStringUtils
  • SecureRandom

UUID

実はUUIDを生成する際に、以下のクラスのどちらを使っても生成できるみたい。
・org.w3c.util.UUID
・java.util.UUID
ただ、org.w3c.util.UUIDの方は毎回10msのsleepを呼んでいる仕様となってるので速度を気にするのであれば、使用しない方がよさそう。
あとUUIDはこっちで文字列は指定できないので、英数字だけとか、記号のみでの文字列をランダム生成したい場合は使えないので注意。
※ちなみに内部ではSecureRandomを使っている。

サンプルコード

  public static void main(String args[]) {
        UUID id = UUID.randomUUID();
        System.out.println(id);
    }

結果:0c15831c-68a4-4df0-89eb-bf992070ed0

java.util.Random

偏りや再現性があることで有名なので、あまり使うということは少ないと思いますが、参考までに。
主に時刻をseedにしてRandomを生成するのが多いのかなと思いますが、実際にRandomの中を見ると、コンストラクタで以下のことをしているので、

public Random() { this(++seedUniquifier + System.nanoTime()); }

よくサンプルで見る、

Random random = new Random(System.currentTimeMillis());

はわざわざ指定する必要はないと思う。

サンプルコード

    public static void main(String args[]) {
        Random random = new Random();
        System.out.println(random.nextDouble());
    }

結果:0.7308781907032909

java.lang.Math#Random

Oracleのドキュメントにもあるように実はRandomよりこちらのが簡単ですよと言っています。
http://docs.oracle.com/javase/jp/6/api/java/util/Random.html
ただ、こちらも実際は内部でjava.util.Randomを使用しているので、偏りという点では極力使用は控えた方がいいかもしれない。

サンプルコード

    public static void main(String args[]) {
        System.out.println(Math.random());
    }

結果:0.9219827452200374

org.apache.commons.lang3.RandomStringUtils

便利なランダム文字列生成クラスといった感じ。
英字だけ、数字だけ、文字数指定、とかを簡単に記述できる点ではかなり便利だけど、残念なことに他と同様で内部でjava.util.Randomを使ってるので、生成した文字列がクリティカルな場合はやっぱり極力使用はしない方がいい。

サンプルコード

    public static void main(String args[]) {
    
        // 半角数字を使用でランダム文字列を生成
        String random1 = RandomStringUtils.randomNumeric(10);
    
        // 半角アルファベット + 半角数字でランダム文字列を生成
        String random2 = RandomStringUtils.randomAlphanumeric(10);
    
        // 指定した文字列でランダム文字列を生成
        String random3 = RandomStringUtils.random(10, "ABC123!%&'");
    
        System.out.println(random1);
        System.out.println(random2);
        System.out.println(random3);
    }

結果:
random1:0504193294
random2:vd1FjlZDGg
random3:3'&%A'!C2A

java.security.SecureRandom

Randomと違って、暗号アルゴリズムを設定することができるジェネレーターで、OSのによって使うアルゴリズムが違うらしくwindowsだと"SHA1PRNG"、Linuxだと"NativePRNG"がデフォルト。
もちろん直接指定することができる。
ただ、"NativePRNG"の方が処理が遅いという情報があったりするので、一般的な"SHA1PRNG"指定した方がいいのかなって思います。

また、java.security.SecureRandom.nextBytes(byte[])
を呼ぶとセキュアな方法でseedを自動的に設定できるので、最終的には以下の様な実装になる。

※SHA1PRNGを/dev/randomから読み込もうとしても/dev/urandomが選択されてしまうバグがあるらしいので参考までに。http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6202721

サンプルコード

  public static void main(String args[])  {

      SecureRandom secRandom = null;
      byte bytes[] = new byte[16];
        try {
            secRandom = SecureRandom.getInstance("SHA1PRNG");
            secRandom.nextBytes(bytes);
        } catch (NoSuchAlgorithmException e) {
          System.out.println("そんなアルゴリズムはないよ");          
        }        
        System.out.println(secRandom.nextDouble());
    }

結果:0.9546535293824232

結論

最終的に何を目的としてランダム文字列を生成するかによって、選択するものが変わってくるのかなと思う。
パフォーマンスよりセキュリティ重視なら間違いなくSecureRandomとかUUID使うことになるだろうし、あまりないかもしれないけど、単純にランダム文字列を生成したいのであれば、上記の好きな乱数発生器を使用して問題ないはず。
 
 英語の記事ですが、以下のサイトは結構深いところに突っ込んでいるので、よかったら参考にしてみてください。
 ・http://www.cigital.com/justice-league-blog/2009/08/14/proper-use-of-javas-securerandom/
 ・http://moi.vonos.net/java/securerandom/
 ・http://resources.infosecinstitute.com/random-number-generation-java/