我一直在寻找一种简单的 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(); }
如果您乐意使用Apache类,可以使用org.apache.commons.text.RandomStringGenerator
(commons-text).
例:
RandomStringGenerator randomStringGenerator = new RandomStringGenerator.Builder() .withinRange('0', 'z') .filteredBy(CharacterPredicates.LETTERS, CharacterPredicates.DIGITS) .build(); randomStringGenerator.generate(12); // toUpperCase() if you want
因为commons-lang 3.6,RandomStringUtils
不推荐使用.
在一行中:
Long.toHexString(Double.doubleToLongBits(Math.random()));
http://mynotes.wordpress.com/2009/07/23/java-generating-random-string/
您可以使用Apache库:RandomStringUtils
RandomStringUtils.randomAlphanumeric(20).toUpperCase();
没有任何外部库,这很容易实现.
1.加密伪随机数据生成首先,您需要加密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.3×10 ^ 36) - 碰撞的机会是"*(...),因为有十亿分之一的重复,103万亿版本必须生成4个UUID 2 ".我们将选择128位,因为它恰好适合16个字节,并且被认为非常适合于基本上每个但是最极端的用例都是唯一的,并且您不必考虑重复.这是一个简单的熵比较表,包括对生日问题的简单分析.
对于简单的要求,8或12字节长度可能就足够了,但是对于16字节,您处于"安全方".
这基本上就是它.最后一件事是考虑编码,因此它可以表示为可打印的文本(阅读,a String
).
典型编码包括:
Base64
每个字符编码6位,产生33%的开销.遗憾的是,JDK中没有标准实现(7及以下 - 有Android和Java 8+).但是存在许多添加此功能的库.缺点是,该标准XfJhfv3C0P6ag7y9VQxSbw==
对于例如不安全.urls和作为大多数文件系统中的文件名需要额外的编码(例如url编码)或使用URL安全版本的Base64.使用填充编码16个字节的示例:Base32
A-Z
每个字符编码5bit,产生40%的开销.这将使用2-7
并WUPIL5DQTZGMF4D3NX5L7LNFOY
使其合理地节省空间,同时不区分大小写的字母数字.JDK中没有标准实现.示例编码16个字节而不填充:Base16
Base32
(十六进制)每个字符编码4bit,每字节需要2个字符(即16字节创建一个长度为32的字符串).因此十六进制的空间小于有效的0-9
,但安全的,因为它在大多数情况下(URL)仅使用用途A
和F
到4fa3dd0f57cb3bf331441ed285b27735
.编码16字节的示例:SecureRandom
.请参阅此处有关转换为十六进制的SO讨论.
其他编码如Base85和异国情调的Base122存在更好/更差的空间效率.你可以创建自己的编码(基本上这个线程中的大多数答案都可以),但是如果你没有非常具体的要求,我会反对它.请参阅Wikipedia文章中的更多编码方案.
4.总结和例子
使用 hex
使用至少16个字节(2 ^ 128)的可能值
根据您的要求进行编码(通常base32
或者long
如果您需要它是字母数字)
别
...使用你的家庭酿造编码:如果他们看到你使用的标准编码而不是奇怪的一次创建字符的循环,那么对其他人来说更好的可维护性和可读性.
...使用UUID:它无法保证随机性; 你浪费了6比特的熵并且有冗长的字符串表示
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); //hex encoding } //generateRandomHexToken(16) -> 2189df7475e96aa3982dbeab266497cd
如果你想要一个随时可用的cli工具,你可以使用骰子:https://github.com/patrickfav/dice
使用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
令人惊讶的是没有人建议它但是:
import java.util.UUID UUID.randomUUID().toString();
简单.
这样做的好处是UUID很好而且很长并且保证几乎不可能发生碰撞.
维基百科有一个很好的解释:
"......只有在未来100年内每秒产生10亿UUID后,创造一个重复的概率大约为50%."
http://en.wikipedia.org/wiki/Universally_unique_identifier#Random_UUID_probability_of_duplicates
前4位是版本类型,2是变体,所以你得到122位随机.因此,如果你想,你可以从最后截断,以减少UUID的大小.这不是推荐的,但你仍然有很多随机性,足以让你的500k记录容易.
一个简单易用的解决方案,但只使用小写和数字:
Random r = new java.util.Random (); String s = Long.toString (r.nextLong () & Long.MAX_VALUE, 36);
大小约为12位数到36位,并且无法进一步改善.当然,您可以追加多个实例.
Java 8中的另一种选择是:
static final Random random = new Random(); // Or SecureRandom static final int startChar = (int) '!'; static final int endChar = (int) '~'; static String randomString(final int maxLength) { final int length = random.nextInt(maxLength + 1); return random.ints(length, startChar, endChar + 1) .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) .toString(); }
public static String generateSessionKey(int length){ String alphabet = new String("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); //9 int n = alphabet.length(); //10 String result = new String(); Random r = new Random(); //11 for (int i=0; i
14> Kristian Kra..:使用UUID是不安全的,因为UUID的某些部分根本不是随机的.@erickson的过程非常简洁,但不会创建相同长度的字符串.以下代码段应该足够了:
/* * The random generator used by this class to create random keys. * In a holder class to defer initialization until needed. */ private static class RandomHolder { static final Random random = new SecureRandom(); public static String randomKey(int length) { return String.format("%"+length+"s", new BigInteger(length*5/*base 32,2^5*/, random) .toString(32)).replace('\u0020', '0'); } }为何选择
length*5
.让我们假设一个长度为1的随机字符串的简单情况,因此是一个随机字符.要获得包含所有数字0-9和字符az的随机字符,我们需要一个0到35之间的随机数来获得每个字符中的一个.BigInteger
提供了一个构造函数来生成一个随机数,均匀分布在该范围内0 to (2^numBits - 1)
.不幸的是35不是2 ^ numBits可以接收的数字 - 1.所以我们有两个选择:或者用2^5-1=31
或者2^6-1=63
.如果我们选择,2^6
我们会得到很多"不必要的"/"更长"的数字.因此2^5
,即使我们松散4个字符(wz),这是更好的选择.现在生成一定长度的字符串,我们可以简单地使用一个2^(length*numBits)-1
数字.最后一个问题,如果我们想要一个具有一定长度的字符串,随机可以生成一个小数字,因此不满足长度,所以我们必须将字符串填充到它所需的长度前置零.
15> 小智..:import java.util.Random; public class passGen{ //Verison 1.0 private static final String dCase = "abcdefghijklmnopqrstuvwxyz"; private static final String uCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String sChar = "!@#$%^&*"; private static final String intChar = "0123456789"; private static Random r = new Random(); private static String pass = ""; public static void main (String[] args) { System.out.println ("Generating pass..."); while (pass.length () != 16){ int rPick = r.nextInt(4); if (rPick == 0){ int spot = r.nextInt(25); pass += dCase.charAt(spot); } else if (rPick == 1) { int spot = r.nextInt (25); pass += uCase.charAt(spot); } else if (rPick == 2) { int spot = r.nextInt (7); pass += sChar.charAt(spot); } else if (rPick == 3){ int spot = r.nextInt (9); pass += intChar.charAt (spot); } } System.out.println ("Generated Pass: " + pass); } }所以这样做只是将密码添加到字符串中......并且工作得很好看看...非常简单.我写的
16> Todd..:我找到了这个生成随机十六进制编码字符串的解决方案 提供的单元测试似乎符合我的主要用例.虽然,它比其他一些答案稍微复杂一些.
/** * 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); } } }
17> Jameskittu..:import java.util.Date; import java.util.Random; public class RandomGenerator { private static Random random = new Random((new Date()).getTime()); public static String generateRandomString(int length) { char[] values = {'a','b','c','d','e','f','g','h','i','j', 'k','l','m','n','o','p','q','r','s','t', 'u','v','w','x','y','z','0','1','2','3', '4','5','6','7','8','9'}; String out = ""; for (int i=0;i
18> deepakmodak..:
根据您的要求更改字符串字符.
字符串是不可变的.这
StringBuilder.append
比字符串连接更有效.
public static String getRandomString(int length) { final String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+"; StringBuilder result = new StringBuilder(); while(length > 0) { Random rand = new Random(); result.append(characters.charAt(rand.nextInt(characters.length()))); length--; } return result.toString(); }
这没有增加先前未提及的数十个答案.并且在循环的每次迭代中创建新的"Random"实例是低效的.
19> 小智..:import java.util.*; import javax.swing.*; public class alphanumeric{ public static void main(String args[]){ String nval,lenval; int n,len; nval=JOptionPane.showInputDialog("Enter number of codes you require : "); n=Integer.parseInt(nval); lenval=JOptionPane.showInputDialog("Enter code length you require : "); len=Integer.parseInt(lenval); find(n,len); } public static void find(int n,int length) { String str1="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; StringBuilder sb=new StringBuilder(length); Random r = new Random(); System.out.println("\n\t Unique codes are \n\n"); for(int i=0;i
20> Patrik Bego..:对于"简单"的解决方案,我真的不喜欢这些答案:S
我会去一个简单的;),纯java,一个班轮(熵基于随机字符串长度和给定的字符集):
public String randomString(int length, String characterSet) { return IntStream.range(0, length).map(i -> new SecureRandom().nextInt(characterSet.length())).mapToObj(randomInt -> characterSet.substring(randomInt, randomInt + 1)).collect(Collectors.joining()); } @Test public void buildFiveRandomStrings() { for (int q = 0; q < 5; q++) { System.out.println(randomString(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"));//charachterSet can basically be anything } }或者(有点可读的旧方式)
public String randomString(int length, String characterSet) { StringBuilder sb = new StringBuilder(); //consider using StringBuffer if needed for (int i = 0; i < length; i++) { int randomInt = new SecureRandom().nextInt(characterSet.length()); sb.append(characterSet.substring(randomInt, randomInt + 1)); } return sb.toString(); } @Test public void buildFiveRandomStrings() { for (int q = 0; q < 5; q++) { System.out.println(randomString(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")); //charachterSet can basically be anything } }但另一方面,你也可以使用具有相当好的熵的UUID(https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions):
UUID.randomUUID().toString().replace("-", "")希望有所帮助.
21> michaelok..:你提到"简单",但是如果其他人正在寻找满足更严格的安全要求的东西,你可能想看看jpwgen.jpwgen 在Unix中以pwgen为模型,并且非常易于配置.