我一直在寻找一个简单的Java算法来生成伪随机的字母数字字符串。在我的情况下,它将被用作唯一的会话/键标识符,可能在500K+次生成中是唯一的(我的需求并不需要更复杂的东西)。
理想情况下,我希望能够根据我的唯一性需求指定长度。例如,长度为12的生成字符串可能看起来像"AEYGF7K0DM1X"
。
我一直在寻找一个简单的Java算法来生成伪随机的字母数字字符串。在我的情况下,它将被用作唯一的会话/键标识符,可能在500K+次生成中是唯一的(我的需求并不需要更复杂的东西)。
理想情况下,我希望能够根据我的唯一性需求指定长度。例如,长度为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或更少。
计算显示,在500k个15个字符的标识符中发生碰撞的机会约为2 -52 ,这可能比来自宇宙射线等的未检测到的错误更不可能发生。
根据其规范,UUID不是设计为不可预测的,并且不应用作会话标识符。
以标准格式表示的UUID占用了很多空间:仅122位熵需要36个字符。(“随机”UUID的并非所有位都是随机选择的。)随机选择的字母数字字符串仅需21个字符即可打包更多的熵。
UUID不够灵活;它们具有标准化的结构和布局。这既是它们的主要优点,也是它们的主要弱点。在与外部方合作时,UUID提供的
return new BigInteger(130, random).toString(32);
这行代码末尾添加.replaceAll("\\d", " ");
来进行正则表达式替换。它将所有数字替换为空格。对我来说效果很好:我正在将其用作前端Lorem Ipsum的替代品。 - weisjohnSecureRandom
实例分配给 random
变量。 - ericksonBigInteger.toString(int)
并不是那样工作的,它实际上调用了 Long.toString(long, String)
以确定字符值(这提供了更好的 JavaDoc 描述它实际上做了什么)。基本上,执行 BigInteger.toString(32)
意味着你只能得到字符 0-9
+ a-v
,而不是 0-9
+ a-z
。 - ValaJava提供了一种直接做到这一点的方法。如果您不想要破折号,可以轻松地将其去除。只需使用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
UUID.randomUUID().toString().replaceAll("-", "");
将字符串变成字母数字混合格式,与所请求的一致。 - Numidstatic 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();
}
SecureRandom
类代替Random
类。如果密码是在服务器上生成的,则可能容易受到时间攻击。 - foensAB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
还有其他一些允许的字符。 - ACVstatic Random rnd = new Random();
放在方法内部? - MicroRandom
对象是否有充分的理由?我认为没有。 - cassiomolinorg.apache.commons.text.RandomStringGenerator
(Apache 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
已被弃用。
Apache Commons Lang 3.3.1
库中提到的那个类,发现它仅使用java.util.Random
来生成随机序列,因此会产生不安全的序列。 - Yuriy Nakonechnyypublic static java.lang.String random(int count, int start, int end, boolean letters, boolean numbers, @Nullable char[] chars, java.util.Random random)
- Ruslans UralovsRandomStringGenerator
,以便序列是安全的:new RandomStringGenerator.Builder().usingRandom(RANDOM::nextInt).build();
- Rohan你可以使用Apache Commons库来实现这个功能,RandomStringUtils是其中一个可选的工具:
RandomStringUtils.randomAlphanumeric(20).toUpperCase();
compile 'commons-lang:commons-lang:2.6'
这行代码表示使用 Gradle 构建工具的项目将会引用 Commons Lang 库的版本 2.6,用于提供一些常用的、易于操作字符串、数组等功能的代码库。 - younes0AEYGF7K0DM1X
,这不是十六进制。让我感到担忧的是人们经常将字母数字与十六进制混淆,它们并不是同一件事情。 - hfontanezMath.random()
生成0到1之间的double
类型数字,指数部分很少被使用。建议使用random.nextLong
生成随机的long
数字,而不是采用这种丑陋的方法。 - maaartinus这可以轻松实现,无需任何外部库。
首先你需要一个密码学的伪随机数生成器(PRNG)。Java 提供了 SecureRandom
来实现,并且通常使用机器上最好的熵源(例如 /dev/random
)。在这里阅读更多信息。
SecureRandom rnd = new SecureRandom();
byte[] token = new byte[byteLength];
rnd.nextBytes(token);
注意:SecureRandom
是Java中生成随机字节的最慢但最安全的方法。然而,我建议在这里不考虑性能,除非您需要每秒生成数百万个令牌,否则它通常不会对应用程序产生实际影响。
接下来,您必须决定令牌需要多么独特。考虑熵的唯一目的就是确保系统能够抵抗暴力攻击:可能值的空间必须足够大,以至于任何攻击者只能在非荒谬的时间内尝试可忽略的比例的值1。
唯一标识符,如随机UUID
,具有122位的熵(即2^122 = 5.3x10^36) - 碰撞的机会是“*(…)要想有十亿分之一的重复几率,必须生成103万亿个版本4的UUID2”。我们将选择128位,因为它恰好适合16字节,并且被认为对于基本上每个使用案例都是非常充足的唯一性,而且您不必考虑重复。这是一个包括简单分析的熵比较表生日问题。
对于简单的要求,8或12字节的长度可能足够,但使用16字节更加“安全”。String
)。
Base64
每个字符编码6位,创建了33%的额外开销。幸运的是,在Java 8+和Android中有标准实现。对于旧版本的Java,您可以使用任何众多的第三方库。如果您希望您的令牌在URL中安全使用,请使用RFC4648的URL安全版本(大多数实现通常都支持)。例如,使用填充编码16字节:XfJhfv3C0P6ag7y9VQxSbw==
Base32
每个字符编码5位,创建了40%的额外开销。它将使用A-Z
和2-7
,使其在占用空间方面相当高效,同时又不区分大小写的字母数字。在JDK中没有任何标准实现。例如,不使用填充编码16字节:WUPIL5DQTZGMF4D3NX5L7LNFOY
Base16
(十六进制)每个字符编码四位,每个字节需要两个字符(即,16字节创建一个长度为32的字符串)。因此,十六进制比Base32
占用更多的空间,但在大多数情况下(URL)使用是安全的,因为它只使用0-9
和A
到F
。例如,编码16字节:4fa3dd0f57cb3bf331441ed285b27735
。在这里查看有关转换为十六进制的Stack Overflow讨论。
SecureRandom
- 使用至少16字节(2^128)的可能值
- 根据您的要求进行编码(通常使用hex
或base32
,如果您需要它是字母数字)
不要
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 (omits leading zeros)
}
//generateRandomHexToken(16) -> 2189df7475e96aa3982dbeab266497cd
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
如果您想要一个现成的命令行工具,您可以使用dice:
如果您已经拥有一个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);
BigInteger
:使用 BigInteger(1, token)
替代 BigInteger(token)
。 - francoisrimport java.security.SecureRandom;
和import java.math.BigInteger;
才能使示例正常工作,但它运行得非常好! - anothermhnew SecureRandom()
使用/dev/urandom
。 - Patrick使用 Dollar 库应该很简单:
// "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
这是 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
Random#nextInt
或者 nextLong
代替。如果需要更高的安全性可以考虑切换到 SecureRandom
。 - maaartinus一种简短而易于实现的解决方案,但它只使用小写字母和数字:
Random r = new java.util.Random ();
String s = Long.toString (r.nextLong () & Long.MAX_VALUE, 36);
这个大小是基于36进制的12位数字,无法再进一步提高。当然你可以添加多个实例。
Long.toString(Math.abs(r.nextLong()), 36);
- Ray Hulhaabs
的问题。这对所有值都适用。 - Radiodef<< 1 >>> 1
。 - shmosel
Long.toHexString(Double.doubleToLongBits(Math.random()));
生成一个16进制的随机字符串。UUID.randomUUID().toString();
生成一个随机唯一标识符。RandomStringUtils.randomAlphanumeric(12);
生成一个包含12个字符的随机字母数字组合。 - Manindar