我一直在寻找一种简单的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中或用手重新输入,可能会引起编码问题。
会话标识符的随机性或熵的基本来源应该来自为密码学设计的随机数发生器。然而,初始化这些生成器有时在计算上很昂贵或很慢,所以在可能的情况下应努力重新使用它们。
不是每个应用都需要安全。随机分配可以是一种有效的方式,让多个实体在一个共享空间中产生标识符,而不需要任何协调或分割。协调可能很慢,特别是在集群或分布式环境中,当实体最终拥有的份额过小或过大时,分割空间会导致问题。
如果攻击者可能会查看和操纵这些标识,那么在没有采取措施使其不可预测的情况下产生的标识应该通过其他方式进行保护,这在大多数网络应用中都会发生。应该有一个单独的授权系统来保护那些标识符可以被没有访问许可的攻击者猜到的对象。
还必须注意使用足够长的标识符,以便在预期的标识符总数的情况下,使碰撞变得不太可能。这被称为"生日悖论"碰撞的概率, p,大约是n2/(2qx),其中n是实际生成的标识符的数量,q是字母表中不同符号的数量,而x是标识符的长度。这应该是一个非常小的数字,比如2‑50或更少。
这样算下来,50万个15个字符的标识符之间发生碰撞的机会大约是2‑52,这可能比宇宙射线等未检测到的错误要少。
根据他们的规范,UUIDs不是被设计成不可预测的,而且不应该作为会话标识符使用。
标准格式的UUIDs占用了大量的空间:36个字符只有122比特的熵。(并不是所有的UUID都是随机选择的。)一个随机选择的字母数字字符串在短短的21个字符中包含了更多的熵。
UUIDs并不灵活;它们有一个标准化的结构和布局。这是它们的主要优点,也是它们的主要弱点。当与外界合作时,UUIDs所提供的标准化可能是有帮助的。对于纯粹的内部使用,它们可能是低效的。
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
这是在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