如何生成随机字母数字字符串?

问题描述 投票:1601回答:45

我一直在寻找一种简单的Java算法来生成伪随机字母数字字符串。在我的情况下,它将被用作一个独特的会话/密钥标识符,它可能在500K+生成中“可能”是唯一的(我的需求实际上并不需要更复杂的东西)。

理想情况下,我可以根据我的唯一性需求指定长度。例如,生成的长度为12的字符串可能看起来像"AEYGF7K0DM1X"

java string random alphanumeric
45个回答
1493
投票

算法

要生成随机字符串,请连接从可接受符号集中随机绘制的字符,直到字符串达到所需长度。

履行

这是一些用于生成随机标识符的相当简单且非常灵活的代码。阅读以下重要应用说明的信息。

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应用程序中一样。应该有一个单独的授权系统来保护攻击者可以在没有访问权限的情况下猜出其标识符的对象。

还必须注意使用足够长的标识符,以便在给定预期的标识符总数的情况下不太可能发生冲突。这被称为“生日悖论”。 The probability of a collision, p约为n2 /(2qx),其中n是实际生成的标识符的数量,q是字母表中不同符号的数量,x是标识符的长度。这应该是一个非常小的数字,如2-50或更少。

解决这个问题表明,500k 15个字符标识符之间发生碰撞的可能性大约为2-52,这可能不如宇宙射线未检测到的错误等。

与UUID的比较

根据他们的规范,UUID不是不可预测的,不应该用作会话标识符。

标准格式的UUID需要占用大量空间:36个字符,仅有122位熵。 (并非随机选择“随机”UUID的所有位。)随机选择的字母数字字符串仅包含21个字符的更多熵。

UUID不灵活;他们有一个标准化的结构和布局。这是他们的主要优点,也是他们的主要弱点。与外部团队合作时,UUID提供的标准化可能会有所帮助。对于纯粹的内部使用,它们可能效率低下。


32
投票

令人惊讶的是没有人建议它但是:

scala> RandomAlphaNum.gen(42)
res3: java.lang.String = uja6snx21bswf9t89s00bxssu8g6qlu16ffzqaxxoy

简单。

这样做的好处是UUID很好而且很长并且保证几乎不可能碰撞。

维基百科有一个很好的解释:

“......只有在未来100年内每秒产生10亿UUID后,创造一个重复的概率大约为50%。”

import java.util.UUID UUID.randomUUID().toString();

前4位是版本类型,2是变体,所以你得到122位随机。因此,如果你想,你可以从最后截断,以减少UUID的大小。这不是推荐的,但你仍然有很多随机性,足以让你的500k记录容易。


28
投票

一个简单易用的解决方案,但只使用小写和数字:

http://en.wikipedia.org/wiki/Universally_unique_identifier#Random_UUID_probability_of_duplicates

大小约为12位数到36位,并且无法进一步改善。当然,您可以追加多个实例。


15
投票

Java 8中的另一种选择是:

Random r = new java.util.Random ();
String s = Long.toString (r.nextLong () & Long.MAX_VALUE, 36);

11
投票
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();
}

11
投票

使用UUID是不安全的,因为UUID的某些部分根本不是随机的。 @erickson的过程非常简洁,但不会创建相同长度的字符串。以下代码段应该足够了:

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<length; i++) //12
    result = result + alphabet.charAt(r.nextInt(n)); //13

return result;
}

为什么选择/* * 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'); } } 。让我们假设一个长度为1的随机字符串的简单情况,因此是一个随机字符。要获得包含所有数字0-9和字符a-z的随机字符,我们需要一个0到35之间的随机数来获取每个字符中的一个。 length*5提供了一个构造函数来生成一个随机数,均匀分布在BigInteger范围内。不幸的是35不是2 ^ numBits可以接收的数字 - 1.所以我们有两个选择:要么使用0 to (2^numBits - 1),要么使用2^5-1=31。如果我们选择2^6-1=63,我们会得到很多“不必要的”/“更长”的数字。因此,即使我们松散4个字符(w-z),2^6也是更好的选择。要生成一定长度的字符串,我们可以简单地使用2^5数字。最后一个问题,如果我们想要一个具有一定长度的字符串,随机可以生成一个小数字,因此不满足长度,所以我们必须将字符串填充到它所需的长度前置零。


10
投票
2^(length*numBits)-1

所以这样做只是将密码添加到字符串中......并且工作得很好看看...非常简单。我写的


8
投票

我找到了这个生成随机十六进制编码字符串的解决方案提供的单元测试似乎符合我的主要用例。虽然,它比一些其他答案稍微复杂一些。

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);
    }
}

8
投票
/**
 * 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);
        }
    }
}

7
投票
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<length;i++) {
          int idx=random.nextInt(values.length);
          out += values[idx];
      }
      return out;
    }
}

7
投票
  1. 根据您的要求更改字符串字符。
  2. 字符串是不可变的。这里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<n;i++){ for(int j=0;j<length;j++){ sb.append(str1.charAt(r.nextInt(str1.length()))); } System.out.println(" "+sb.toString()); sb.delete(0,length); } } } 比字符串连接更有效。

StringBuilder.append

783
投票

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

7
投票

对于“简单”的解决方案,我真的不喜欢这些答案:S

我会去一个简单的;),纯java,一个班轮(熵基于随机字符串长度和给定的字符集):

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();
    }

或者(有点可读的旧方式)

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

希望有所帮助。


6
投票

你提到“简单”,但为了防止其他人正在寻找满足更严格的安全要求的东西,你可能想看看UUID.randomUUID().toString().replace("-", "") 。 jpwgen是在Unix中使用jpwgen建模的,并且非常易于配置。


4
投票

这是一个Scala解决方案:

pwgen

4
投票

您可以使用UUID类及其getLeastSignificantBits()消息来获取64位随机数据​​,然后将其转换为基数为36的数字(即由0-9,A-Z组成的字符串):

(for (i <- 0 until rnd.nextInt(64)) yield { 
  ('0' + rnd.nextInt(64)).asInstanceOf[Char] 
}) mkString("")

这会产生一个长达13个字符的字符串。我们使用Math.abs()来确保没有潜入的减号。


4
投票

如果您的密码必须包含数字字母特殊字符,您可以使用以下代码:

Long.toString(Math.abs( UUID.randomUUID().getLeastSignificantBits(), 36));

4
投票

这是private static final String NUMBERS = "0123456789"; private static final String UPPER_ALPHABETS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String LOWER_ALPHABETS = "abcdefghijklmnopqrstuvwxyz"; private static final String SPECIALCHARACTERS = "@#$%&*"; private static final int MINLENGTHOFPASSWORD = 8; public static String getRandomPassword() { StringBuilder password = new StringBuilder(); int j = 0; for (int i = 0; i < MINLENGTHOFPASSWORD; i++) { password.append(getRandomPasswordCharacters(j)); j++; if (j == 3) { j = 0; } } return password.toString(); } private static String getRandomPasswordCharacters(int pos) { Random randomNum = new Random(); StringBuilder randomChar = new StringBuilder(); switch (pos) { case 0: randomChar.append(NUMBERS.charAt(randomNum.nextInt(NUMBERS.length() - 1))); break; case 1: randomChar.append(UPPER_ALPHABETS.charAt(randomNum.nextInt(UPPER_ALPHABETS.length() - 1))); break; case 2: randomChar.append(SPECIALCHARACTERS.charAt(randomNum.nextInt(SPECIALCHARACTERS.length() - 1))); break; case 3: randomChar.append(LOWER_ALPHABETS.charAt(randomNum.nextInt(LOWER_ALPHABETS.length() - 1))); break; } return randomChar.toString(); } 的一行代码

AbacusUtil

随机并不意味着它必须是唯一的。使用以下方法获取唯一字符串:

String.valueOf(CharStream.random('0', 'z').filter(c -> N.isLetterOrDigit(c)).limit(12).toArray())

3
投票

使用apache库可以在一行中完成

N.uuid() // e.g.: "e812e749-cf4c-4959-8ee1-57829a69a80f". length is 36.
N.guid() // e.g.: "0678ce04e18945559ba82ddeccaabfcd". length is 32 without '-'

这里是doc import org.apache.commons.lang.RandomStringUtils; RandomStringUtils.randomAlphanumeric(64);



3
投票

我认为这是最小的解决方案,或者几乎是最小的解决方案之一:

public static String randomSeriesForThreeCharacter() {
    Random r = new Random();
    String value="";
    char random_Char ;
    for(int i=0; i<10;i++)
    { 
        random_Char = (char) (48 + r.nextInt(74));
        value=value+random_char;
    }
    return value;
}

代码工作得很好。如果您使用此方法,我建议您使用超过10个字符。碰撞发生在5个字符/ 30362次迭代中。这花了9秒。


2
投票
 public String generateRandomString(int length) {
    String randomString = "";

    final char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890".toCharArray();
    final SecureRandom random = new SecureRandom();
    for (int i = 0; i < length; i++) {
        randomString = randomString + chars[random.nextInt(chars.length)];
    }

    return randomString;
}

522
投票
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();
}

2
投票

也许这很有帮助

public static String getRandomString(int length) 
{
   String randomStr = UUID.randomUUID().toString();
   while(randomStr.length() < length) {
       randomStr += UUID.randomUUID().toString();
   }
   return randomStr.substring(0, length);
}

475
投票

如果您乐意使用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被弃用。


104
投票

在一行中:

Long.toHexString(Double.doubleToLongBits(Math.random()));

http://mynotes.wordpress.com/2009/07/23/java-generating-random-string/


101
投票

您可以使用Apache库:RandomStringUtils

RandomStringUtils.randomAlphanumeric(20).toUpperCase();

63
投票

没有任何外部库,这很容易实现。

1. Cryptographic Pseudo Random Data Generation

首先,您需要加密PRNG。 Java有SecureRandom,因为它通常使用机器上最好的熵源(例如/dev/random)。 Read more here.

SecureRandom rnd = new SecureRandom();
byte[] token = new byte[byteLength];
rnd.nextBytes(token);

注意:SecureRandom是Java中生成随机字节的最慢但最安全的方式。但我建议不要考虑此处的性能,因为它通常对您的应用程序没有实际影响,除非您必须每秒生成数百万个令牌。

2. Required Space of Possible Values

接下来,您必须确定您的令牌需要“多么独特”。考虑熵的唯一要点是确保系统能够抵抗暴力攻击:可能值的空间必须如此之大,以至于任何攻击者只能在非荒谬的时间范围内尝试可忽略不计的值。诸如随机1之类的唯一标识符具有122bit的熵(即2 ^ 122 = 5.3x10 ^ 36) - 碰撞的机会是“*(...),因为有十亿分之一的重复,103万亿版本4 UUID必须生成UUID“。我们将选择128位,因为它完全适合16个字节,并且被视为2,因为它基本上是唯一的,但是最极端的用例,你不必考虑重复。这是一个简单的熵比较表,包括对highly sufficient的简单分析。

birthday problem

对于简单的要求,8或12字节长度可能就足够了,但是对于16字节,您处于“安全方”。

这基本上就是它。最后一件事是考虑编码,因此它可以表示为可打印的文本(阅读,comparison of token sizes)。

3. Binary to Text Encoding

典型编码包括:

  • String每个字符编码6bit,产生33%的开销。幸运的是,Base64Java 8+中有标准实现。使用较旧的Java,您可以使用任何Android。如果您希望您的令牌是url安全的,请使用RFC4648的numerous third party libraries版本(大多数实现通常都支持这种版本)。使用填充编码16个字节的示例:url-safe
  • XfJhfv3C0P6ag7y9VQxSbw==每个字符编码5bit,产生40%的开销。这将使用Base32A-Z使其合理地节省空间,同时不区分大小写的字母数字。没有2-7。示例编码16个字节而不填充:standard implementation in the JDK
  • WUPIL5DQTZGMF4D3NX5L7LNFOY(十六进制)每个字符编码4bit,每个字节需要2个字符(即16字节创建一个长度为32的字符串)。因此,hex比Base16的空间效率低,但在大多数情况下(url)使用是安全的,因为它只使用Base320-9A。编码16个字节的示例:F4fa3dd0f57cb3bf331441ed285b27735

其他编码如See a SO discussion about converting to hex here.和异国情调的Base85存在更好/更差的空间效率。你可以创建自己的编码(基本上这个线程中的大多数答案都可以),但是如果你没有非常具体的要求,我会反对它。见Base122

4. Summary and Example

  • ...使用你的家庭酿造编码:如果他们看到你使用的标准编码而不是奇怪的一次创建字符的循环,那么对其他人来说更好的可维护性和可读性。
  • ...使用UUID:它无法保证随机性;你浪费了6比特的熵并且有冗长的字符串表示

示例:十六进制令牌生成器

base32

示例:Base64令牌生成器(Url安全)

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

示例:Java CLI工具

如果你想要一个随时可用的cli工具,你可以使用骰子: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


42
投票

使用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));
    }
}

32
投票

在Java中:

DKL1SBH9UJWC
JH7P0IT21EA5
5DTI72EO6SFU
HQUMJTEBNF7Y
1HCR6SKYWGT7

这是一个示例运行:

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();
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.