我一直在寻找一种简单的Java算法来生成伪随机字母数字字符串。在我的情况下,它将被用作唯一的会话/密钥标识符,“很可能”在超过50万代的时间内是唯一的(我的需求实际上不需要更复杂的东西)。
理想情况下,我可以根据我的独特性需求指定长度。例如,生成的长度为12的字符串可能看起来像“AEYGF7K0DM1X”。
我一直在寻找一种简单的Java算法来生成伪随机字母数字字符串。在我的情况下,它将被用作唯一的会话/密钥标识符,“很可能”在超过50万代的时间内是唯一的(我的需求实际上不需要更复杂的东西)。
理想情况下,我可以根据我的独特性需求指定长度。例如,生成的长度为12的字符串可能看起来像“AEYGF7K0DM1X”。
当前回答
算法
要生成随机字符串,请连接从可接受符号集合中随机抽取的字符,直到字符串达到所需长度。
实施
这里有一些非常简单且非常灵活的代码,用于生成随机标识符。阅读以下信息,了解重要的应用注意事项。
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约为n2/(2qx),其中n是实际生成的标识符的数量,q是字母表中不同符号的数量,x是标识符的长度。这应该是一个非常小的数字,比如2‑50或更少。
计算结果表明,500k15个字符的标识符之间发生冲突的可能性约为2-52,这可能比宇宙射线等未检测到的错误更不可能。
与UUID的比较
根据他们的规范,UUID不是设计为不可预测的,不应该用作会话标识符。
标准格式的UUID占用了大量空间:36个字符只代表122位熵。(并非“随机”UUID的所有位都是随机选择的。)随机选择的字母数字字符串仅在21个字符中包含更多的熵。
UUID不灵活;它们具有标准化的结构和布局。这是他们的主要优点,也是他们的主要弱点。与外部合作时,UUID提供的标准化可能会有所帮助。对于纯内部使用,它们可能效率低下。
其他回答
我找到了生成随机十六进制编码字符串的解决方案。所提供的单元测试似乎符合我的主要用例。虽然,它比提供的一些其他答案稍微复杂一些。
/**
* Generate a random hex encoded string token of the specified length
*
* @param length
* @return random hex string
*/
public static synchronized String generateUniqueToken(Integer length){
byte random[] = new byte[length];
Random randomGenerator = new Random();
StringBuffer buffer = new StringBuffer();
randomGenerator.nextBytes(random);
for (int j = 0; j < random.length; j++) {
byte b1 = (byte) ((random[j] & 0xf0) >> 4);
byte b2 = (byte) (random[j] & 0x0f);
if (b1 < 10)
buffer.append((char) ('0' + b1));
else
buffer.append((char) ('A' + (b1 - 10)));
if (b2 < 10)
buffer.append((char) ('0' + b2));
else
buffer.append((char) ('A' + (b2 - 10)));
}
return (buffer.toString());
}
@Test
public void testGenerateUniqueToken(){
Set set = new HashSet();
String token = null;
int size = 16;
/* Seems like we should be able to generate 500K tokens
* without a duplicate
*/
for (int i=0; i<500000; i++){
token = Utility.generateUniqueToken(size);
if (token.length() != size * 2){
fail("Incorrect length");
} else if (set.contains(token)) {
fail("Duplicate token generated");
} else{
set.add(token);
}
}
}
public static String getRandomString(int length)
{
String randomStr = UUID.randomUUID().toString();
while(randomStr.length() < length) {
randomStr += UUID.randomUUID().toString();
}
return randomStr.substring(0, length);
}
您可以在没有外部库的情况下在一行中完成此操作。
int length = 12;
String randomString = new Random().ints(48, 122).filter(i -> (i < 58 || i > 64) && (i < 91 || i > 96)).limit(length).collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
System.out.print(randomString);
我将长度分隔成一个参数,并添加了一行以打印结果。
此代码创建一个以字母数字ascii范围为界的随机整数流。然后它过滤掉一些符号,因为字母数字范围不是连续的。然后它限制长度并将结果收集到字符串中。
因为这种方法丢弃了它生成的大约20%的数字/字符(因为它们是符号),所以对性能的影响很小。
我不觉得它特别可读,但我认为没有人在一行中提出过本机Java解决方案。
public class Utils {
private final Random RANDOM = new SecureRandom();
private final String ALPHABET = "0123456789QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm";
private String generateRandomString(int length) {
StringBuffer buffer = new StringBuffer(length);
for (int i = 0; i < length; i++) {
buffer.append(ALPHABET.charAt(RANDOM.nextInt(ALPHABET.length())));
}
return new String(buffer);
}
}
我开发了一个应用程序,为我的项目开发一个自动生成的字母数字字符串。在这个字符串中,前三个字符是字母,后七个字符是整数。
public class AlphaNumericGenerator {
public static void main(String[] args) {
java.util.Random r = new java.util.Random();
int i = 1, n = 0;
char c;
String str = "";
for (int t = 0; t < 3; t++) {
while (true) {
i = r.nextInt(10);
if (i > 5 && i < 10) {
if (i == 9) {
i = 90;
n = 90;
break;
}
if (i != 90) {
n = i * 10 + r.nextInt(10);
while (n < 65) {
n = i * 10 + r.nextInt(10);
}
}
break;
}
}
c = (char)n;
str = String.valueOf(c) + str;
}
while(true){
i = r.nextInt(10000000);
if(i > 999999)
break;
}
str = str + i;
System.out.println(str);
}
}