アルゴリズム
ランダムな文字列を生成するには、文字列が目的の長さに達するまで、受け入れ可能な記号のセットからランダムに描かれた文字を連結します。
実装
ランダムな識別子を生成するためのかなり単純で非常に柔軟なコードを次に示します。重要なアプリケーションノートについては、以下の情報をお読みください。
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によって提供される標準化が役立つ場合があります。純粋に内部で使用する場合、非効率になる可能性があります。