ランダムな英数字の文字列を生成する方法

2008年09月03日に質問されました。  ·  閲覧回数 1.4M回  ·  ソース

Todd picture
2008年09月03日

疑似乱数の英数字文字列を生成するための単純なJavaアルゴリズムを探していました。 私の状況では、 500K+世代にわたって一意である可能性が高い一意のセッション/キー識別子として使用されます(私のニーズには、これ以上高度なものは必要ありません)。

理想的には、独自性のニーズに応じて長さを指定できます。 たとえば、生成された長さ12の文字列は、 "AEYGF7K0DM1X"ます。

回答

erickson picture
2008年09月03日
1563

アルゴリズム

ランダムな文字列を生成するには、文字列が目的の長さに達するまで、受け入れ可能な記号のセットからランダムに描画された文字を連結します。

実装

ランダムな識別子を生成するための非常に単純で非常に柔軟なコードを次に示します。 重要なアプリケーションノートについては、以下の情報をお読みください

public class RandomString {

    /**
     * Generate a random string.
     */
    public String nextString() {
        for (int idx = 0; idx < buf.length; ++idx)
            buf[idx] = symbols[random.nextInt(symbols.length)];
        return new String(buf);
    }

    public static final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    public static final String lower = upper.toLowerCase(Locale.ROOT);

    public static final String digits = "0123456789";

    public static final String alphanum = upper + lower + digits;

    private final Random random;

    private final char[] symbols;

    private final char[] buf;

    public RandomString(int length, Random random, String symbols) {
        if (length < 1) throw new IllegalArgumentException();
        if (symbols.length() < 2) throw new IllegalArgumentException();
        this.random = Objects.requireNonNull(random);
        this.symbols = symbols.toCharArray();
        this.buf = new char[length];
    }

    /**
     * Create an alphanumeric string generator.
     */
    public RandomString(int length, Random random) {
        this(length, random, alphanum);
    }

    /**
     * Create an alphanumeric strings from a secure generator.
     */
    public RandomString(int length) {
        this(length, new SecureRandom());
    }

    /**
     * Create session identifiers.
     */
    public RandomString() {
        this(21);
    }

}

使用例

8文字の識別子用の安全でないジェネレーターを作成します。

RandomString gen = new RandomString(8, ThreadLocalRandom.current());

セッション識別子用の安全なジェネレーターを作成します。

RandomString session = new RandomString();

印刷用の読みやすいコードを使用してジェネレーターを作成します。 文字列は、使用する記号の数を減らすために、完全な英数字の文字列よりも長くなっています。

String easy = RandomString.digits + "ACEFGHJKLMNPQRUVWXYabcdefhijkprstuvwx";
RandomString tickets = new RandomString(23, new SecureRandom(), easy);

セッション識別子として使用

一意である可能性が高いセッション識別子を生成するだけでは不十分です。または、単純なカウンターを使用することもできます。 予測可能な識別子が使用されると、攻撃者はセッションをハイジャックします。

長さと安全性の間には緊張関係があります。 可能性が少ないため、識別子が短いほど推測しやすくなります。 ただし、識別子が長いほど、より多くのストレージと帯域幅が消費されます。 記号のセットを大きくすると役立ちますが、識別子がURLに含まれている場合や手動で再入力した場合は、エンコードの問題が発生する可能性があります。

セッション識別子のランダム性またはエントロピーの根本的な原因は、暗号化用に設計された乱数ジェネレーターから取得する必要があります。 ただし、これらのジェネレーターの初期化は、計算コストが高くなるか、時間がかかる場合があるため、可能な場合は再利用するように努力する必要があります。

オブジェクト識別子として使用

すべてのアプリケーションがセキュリティを必要とするわけではありません。 ランダムな割り当ては、複数のエンティティが調整や分割を行わずに共有スペースで識別子を生成するための効率的な方法です。 特にクラスター環境または分散環境では、調整が遅くなる可能性があり、スペースを分割すると、エンティティの共有が小さすぎたり大きすぎたりする場合に問題が発生します。

ほとんどのWebアプリケーションで発生するように、攻撃者が識別子を表示および操作できる可能性がある場合は、予測不可能にする手段を講じずに生成された識別子を他の手段で保護する必要があります。 アクセス許可なしに攻撃者が識別子を推測できるオブジェクトを保護する別の認証システムが必要です。

予想される識別子の総数を考えると、衝突が発生する可能性が低いほど長い識別子を使用するように注意する必要もあります。 これは「誕生日のパラドックス」と呼ばれます。 衝突の確率pは、およそn 2 /(2q x )です。ここで、 nは実際に生成された識別子の数、 qはアルファベットの個別の記号の数、 xは識別子の長さです。 これは、2 ‑50以下などの非常に小さい数にする必要があります。

これを解決すると、500kの15文字の識別子間の衝突の可能性は約2 ‑52であり、宇宙線などからの検出されないエラーよりもおそらく少ない可能性があります。

UUIDとの比較

それらの仕様によれば、 UUIDは予測できないように設計されておらず、セッション識別子として使用しください

標準形式のUUIDは、多くのスペースを必要とします。わずか122ビットのエントロピーに対して36文字です。 (「ランダムな」UUIDのすべてのビットがランダムに選択されるわけではありません。)ランダムに選択された英数字の文字列は、わずか21文字でより多くのエントロピーをパックします。

UUIDは柔軟ではありません。 それらは標準化された構造とレイアウトを持っています。 これが彼らの主な長所であり、主な弱点でもあります。 外部の関係者と協力する場合、UUIDによって提供される標準化が役立つ場合があります。 純粋に内部使用の場合、非効率になる可能性があります。

Steve McLeod picture
2008年09月03日
829

Javaは、これを直接行う方法を提供します。 ダッシュが必要ない場合は、簡単に削除できます。 uuid.replace("-", "")使用するだけです

import java.util.UUID;

public class randomStringGenerator {
    public static void main(String[] args) {
        System.out.println(generateString());
    }

    public static String generateString() {
        String uuid = UUID.randomUUID().toString();
        return "uuid = " + uuid;
    }
}

出力

uuid = 2d7428a6-b58c-4008-8575-f05549f16316
maxp picture
2008年10月01日
571
static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static SecureRandom rnd = new SecureRandom();

String randomString(int len){
   StringBuilder sb = new StringBuilder(len);
   for(int i = 0; i < len; i++)
      sb.append(AB.charAt(rnd.nextInt(AB.length())));
   return sb.toString();
}
cmsherratt picture
2008年09月04日
489

Apacheクラスを使用することに満足している場合は、 org.apache.commons.text.RandomStringGeneratorApache Commons Text )を使用できます。

例:

RandomStringGenerator randomStringGenerator =
        new RandomStringGenerator.Builder()
                .withinRange('0', 'z')
                .filteredBy(CharacterPredicates.LETTERS, CharacterPredicates.DIGITS)
                .build();
randomStringGenerator.generate(12); // toUpperCase() if you want

Apache Commons Lang 3.6以降、 RandomStringUtilsは非推奨になりました。

manish_s picture
2012年07月20日
115

あなたは使用することができますApacheのコモンズ、このためにライブラリーをRandomStringUtilsを

RandomStringUtils.randomAlphanumeric(20).toUpperCase();
anonymous picture
2009年09月17日
105

一行で:

Long.toHexString(Double.doubleToLongBits(Math.random()));

ソース: Java-ランダムな文字列を生成する

Patrick Favre picture
2017年05月28日
83

これは、外部ライブラリがなくても簡単に実現できます。

1.暗号化擬似ランダムデータ生成(PRNG)

まず、暗号化PRNGが必要です。 JavaにはそのためのSecureRandomがあり、通常はマシン上で最高のエントロピーソースを使用します(例: /dev/random )。 詳しくはこちらをご覧ください

SecureRandom rnd = new SecureRandom();
byte[] token = new byte[byteLength];
rnd.nextBytes(token);

注: SecureRandomは最も低速ですが、Javaでランダムバイトを生成する最も安全な方法です。 ただし、1秒あたり数百万のトークンを生成する必要がない限り、通常はアプリケーションに実際の影響がないため、ここではパフォーマンスを考慮しないことをお勧めします。

2.可能な値の必要なスペース

次に、トークンの「一意性」を決定する必要があります。 エントロピーを検討する唯一のポイントは、システムがブルートフォース攻撃に抵抗できることを確認することです。可能な値のスペースは、攻撃者がばかげていない時間に無視できる割合の値しか試すことができないように大きくする必要があります1

ランダムUUIDなどの一意の識別子には122ビットのエントロピーがあります(つまり、2 ^ 122 = 5.3x10 ^ 36)-衝突の可能性は「*(...)」で、10億分の1になります重複の可能性があるため、103兆バージョン4のUUIDを生成する必要があります2 "。 私たちは、それが16バイトに正確にフィットし、と見られている非常に十分な基本的にはすべてのためにユニークなもののために、しかし最も極端な、ユースケースと、あなたは重複を考える必要はありません。 これは、誕生日の問題の簡単な分析を含むエントロピーの簡単な比較表です。

Comparison of token sizes

単純な要件の場合、8バイトまたは12バイトの長さで十分かもしれませんが、16バイトの場合は「安全側」になります。

そしてそれは基本的にそれです。 最後に、印刷可能なテキストとして表現できるようにエンコードについて考えることです(読み取り、 String )。

3.バイナリからテキストへのエンコーディング

一般的なエンコーディングは次のとおりです。

  • Base64すべての文字が6ビットをエンコードし、33%のオーバーヘッドを作成します。 幸い、 Java8以降Androidには標準の実装があります。 古いJavaでは、多数のサードパーティライブラリのいずれかを使用できURLセーフバージョンのRFC4648を使用してください(通常、ほとんどの実装でサポートされています)。 パディングを使用して16バイトをエンコードする例: XfJhfv3C0P6ag7y9VQxSbw==

  • Base32すべての文字が5ビットをエンコードし、40%のオーバーヘッドを作成します。 これはA-Z2-7を使用するため、大文字と小文字を区別しない英数字でありながら、スペース効率がかなり高くなります。 JDKには標準の実装はありません。 パディングなしで16バイトをエンコードする例: WUPIL5DQTZGMF4D3NX5L7LNFOY

  • Base16 (16進数)すべての文字は4ビットをエンコードし、1バイトあたり2文字を必要とします(つまり、16バイトは長さ32の文字列を作成します)。 したがって、16進数はBase32よりもスペース効率が低くなりますが、 0-9AからFのみを使用するため、ほとんどの場合(URL)で安全に使用できます。 。 16バイトのエンコード例: 4fa3dd0f57cb3bf331441ed285b2773516進数への変換については、StackÂOverflowの説明を参照してください

Base85やエキゾチックなBase122のような追加のエンコーディングは、より良い/より悪いスペース効率で存在します。 独自のエンコーディングを作成することもできますが(基本的にこのスレッドのほとんどの回答はそうします)、非常に具体的な要件がない場合は、それをお勧めしません。 ウィキペディアの記事で他のエンコードスキームを参照してください。

4.まとめと例

  • SecureRandom使用する
  • 可能な値を少なくとも16バイト(2 ^ 128)使用してください
  • 要件に応じてエンコードします(通常、英数字にする必要がある場合はhexまたはbase32

しないでください

  • ...自作のエンコーディングを使用する:一度に文字を作成する奇妙なforループではなく、使用している標準のエンコーディングを他の人が見れば、保守性と読みやすさが向上します。
  • ... UUIDを使用します:ランダム性についての保証はありません。

例:16進トークンジェネレーター

public static String generateRandomHexToken(int byteLength) {
    SecureRandom secureRandom = new SecureRandom();
    byte[] token = new byte[byteLength];
    secureRandom.nextBytes(token);
    return new BigInteger(1, token).toString(16); // Hexadecimal encoding
}

//generateRandomHexToken(16) -> 2189df7475e96aa3982dbeab266497cd

例:Base64トークンジェネレーター(URLセーフ)

public static String generateRandomBase64Token(int byteLength) {
    SecureRandom secureRandom = new SecureRandom();
    byte[] token = new byte[byteLength];
    secureRandom.nextBytes(token);
    return Base64.getUrlEncoder().withoutPadding().encodeToString(token); //base64 encoding
}

//generateRandomBase64Token(16) -> EEcCCAYuUcQk7IuzdaPzrg

例:JavaCLIツール

すぐに使用できるCLIツールが必要な場合は、サイコロを使用できます。

例:関連する問題-現在のIDを保護する

使用できるID(エンティティ内の合成long )が既にあるが、内部値を公開したくない場合は、このライブラリを使用して暗号化し、難読化できます: https: //github.com/patrickfav/id-mask

IdMask<Long> idMask = IdMasks.forLongIds(Config.builder(key).build());
String maskedId = idMask.mask(id);
// Example: NPSBolhMyabUBdTyanrbqT8
long originalId = idMask.unmask(maskedId);
dfa picture
2010年02月02日
42

ドルの使用は、次のように簡単にする必要があります。

// "0123456789" + "ABCDE...Z"
String validCharacters = $('0', '9').join() + $('A', 'Z').join();

String randomString(int length) {
    return $(validCharacters).shuffle().slice(length).toString();
}

@Test
public void buildFiveRandomStrings() {
    for (int i : $(5)) {
        System.out.println(randomString(12));
    }
}

次のような出力があります。

DKL1SBH9UJWC
JH7P0IT21EA5
5DTI72EO6SFU
HQUMJTEBNF7Y
1HCR6SKYWGT7
Apocalisp picture
2008年09月03日
35

これはJavaです:

import static java.lang.Math.round;
import static java.lang.Math.random;
import static java.lang.Math.pow;
import static java.lang.Math.abs;
import static java.lang.Math.min;
import static org.apache.commons.lang.StringUtils.leftPad

public class RandomAlphaNum {
  public static String gen(int length) {
    StringBuffer sb = new StringBuffer();
    for (int i = length; i > 0; i -= 12) {
      int n = min(12, abs(i));
      sb.append(leftPad(Long.toString(round(random() * pow(36, n)), 36), n, '0'));
    }
    return sb.toString();
  }
}

実行例は次のとおりです。

scala> RandomAlphaNum.gen(42)
res3: java.lang.String = uja6snx21bswf9t89s00bxssu8g6qlu16ffzqaxxoy
user unknown picture
2012年04月17日
32

短くて簡単な解決策ですが、小文字と数字のみを使用します。

Random r = new java.util.Random ();
String s = Long.toString (r.nextLong () & Long.MAX_VALUE, 36);

サイズはベース36に対して約12桁であり、それ以上改善することはできません。 もちろん、複数のインスタンスを追加できます。