我一直在寻找一种简单的Java算法来生成伪随机字母数字字符串。在我的情况下,它将被用作唯一的会话/密钥标识符,“很可能”在超过50万代的时间内是唯一的(我的需求实际上不需要更复杂的东西)。

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


当前回答

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

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

大小约为12位数,以36为基数,这样就无法进一步改进。当然,您可以附加多个实例。

其他回答

高效而简短。

/**
 * Utility class for generating random Strings.
 */
public interface RandomUtil {

    int    DEF_COUNT = 20;
    Random RANDOM    = new SecureRandom();

    /**
     * Generate a password.
     *
     * @return the generated password
     */
    static String generatePassword() {
        return generate(true, true);
    }

    /**
     * Generate an activation key.
     *
     * @return the generated activation key
     */
    static String generateActivationKey() {
        return generate(false, true);
    }

    /**
     * Generate a reset key.
     *
     * @return the generated reset key
     */
    static String generateResetKey() {
        return generate(false, true);
    }

    static String generate(boolean letters, boolean numbers) {
        int
            start = ' ',
            end   = 'z' + 1,
            count = DEF_COUNT,
            gap   = end - start;
        StringBuilder builder = new StringBuilder(count);

        while (count-- != 0) {
            int codePoint = RANDOM.nextInt(gap) + start;

            switch (getType(codePoint)) {
                case UNASSIGNED:
                case PRIVATE_USE:
                case SURROGATE:
                    count++;
                    continue;
            }

            int numberOfChars = charCount(codePoint);

            if (count == 0 && numberOfChars > 1) {
                count++;
                continue;
            }

            if (letters && isLetter(codePoint)
                || numbers && isDigit(codePoint)
                || !letters && !numbers) {

                builder.appendCodePoint(codePoint);
                if (numberOfChars == 2)
                    count--;
            }
            else
                count++;
        }
        return builder.toString();
    }
}

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

给定一些字符(AllCharacters),您可以随机选择字符串中的一个字符。然后使用for循环重复获取随机字符。

public class MyProgram {
  static String getRandomString(int size) {
      String AllCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
      StringBuilder sb = new StringBuilder(size);
      int length = AllCharacters.length();
      for (int i = 0; i < size; i++) {
          sb.append(AllCharacters.charAt((int)(length * Math.random())));
      }
      return sb.toString();
  }

  public static void main(String[] args) {
      System.out.println(MyProgram.getRandomString(30));
  }
}

在沙盒上试试另请参阅其他语言实现随机字符串生成器

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

这在没有任何外部库的情况下很容易实现。

1.密码伪随机数据生成(PRNG)

首先,您需要加密PRNG。Java具有SecureRandom,通常使用机器上最好的熵源(例如/dev/random)。在这里阅读更多信息。

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

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

2.可能值的所需空间

接下来,你必须决定你的令牌需要“多么独特”。考虑熵的唯一目的是确保系统能够抵御暴力攻击:可能值的空间必须如此之大,以至于任何攻击者只能在非荒谬的时间1内尝试微不足道的一部分值。

唯一标识符(如随机UUID)具有122位熵(即,2^122=5.3x10^36)-冲突的可能性为“*(…),要有十亿分之一的重复机会,必须生成103万亿版本4 UUID 2”。我们将选择128位,因为它正好适合16个字节,而且对于基本上每一个但最极端的用例来说,它都是唯一的,而且您不必考虑重复。这是一个简单的熵比较表,包括生日问题的简单分析。

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

基本上就是这样。最后一件事是考虑编码,以便将其表示为可打印文本(读,字符串)。

3.二进制到文本编码

典型编码包括:

Base64每个字符编码6位,产生33%的开销。幸运的是,Java 8+和Android中有标准实现。对于较旧的Java,您可以使用众多第三方库中的任何一个。如果您希望令牌是URL安全的,请使用RFC4648的URL安全版本(大多数实现通常支持该版本)。使用填充编码16个字节的示例:XfJhfv3C0P6ag7y9VQxSbw==Base32每个字符编码5位,产生40%的开销。这将使用A-Z和2-7,使其具有合理的空间效率,同时不区分大小写。JDK中没有任何标准实现。无填充编码16字节的示例:WUPIL5DQZGMF4D3NX5L7LNFOYBase16(十六进制)每个字符编码四位,每个字节需要两个字符(即,16字节创建长度为32的字符串)。因此,十六进制的空间效率低于Base32,但在大多数情况下(URL)使用它是安全的,因为它只使用0-9和A到F。示例编码16个字节:4fadd0f57cb3bf331441ed285b27735。请参阅此处有关转换为十六进制的堆栈溢出讨论。

诸如Base85和奇异Base122之类的附加编码具有更好/更差的空间效率。您可以创建自己的编码(本主题中的大多数答案都是这样做的),但如果您没有非常具体的要求,我建议您不要这样做。请参阅维基百科文章中的更多编码方案。

4.总结和示例

使用SecureRandom至少使用16字节(2^128)的可能值根据您的要求进行编码(如果需要字母数字,通常为十六进制或32进制)

不要

……使用您自己编写的编码:如果其他人看到您使用的标准编码,而不是每次创建字符时使用的奇怪循环,则可以更好地维护和阅读。…使用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); // Hexadecimal encoding
}

//generateRandomHexToken(16) -> 2189df7475e96aa3982dbeab266497cd

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

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

示例:Java CLI工具

如果您想要现成的CLI工具,可以使用dice:

示例:相关问题-保护当前ID

如果您已经有一个可以使用的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);