我一直在寻找一种简单的 Java 算法来生成伪随机字母数字字符串。在我的情况下,它将被用作一个独特的会话 / 密钥标识符,它可能 “超过 500K + 代”(我的需求并不需要更复杂的东西)。理想情况下,我可以根据我的唯一性需求指定长度。例如,生成的长度为 12 的字符串可能类似于"AEYGF7K0DM1X"
。
要生成随机字符串,请连接从可接受符号集中随机绘制的字符,直到字符串达到所需长度。
这是一些用于生成随机标识符的相当简单且非常灵活的代码。 阅读以下重要应用说明的信息。
import java.security.SecureRandom;
import java.util.Locale;
import java.util.Objects;
import java.util.Random;
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 需要占用大量空间:36 个字符,仅有 122 位熵。 (并非随机选择 “随机”UUID 的所有位。)随机选择的字母数字字符串仅包含 21 个字符的更多熵。
UUID 不灵活; 他们有一个标准化的结构和布局。这是他们的主要优点,也是他们的主要弱点。与外部团队合作时,UUID 提供的标准化可能会有所帮助。对于纯粹的内部使用,它们可能效率低下。
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
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();
}