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


我一直在寻找一个 简单 Java算法生成伪随机字母数字字符串。在我的情况下,它将被用作一个独特的会话/密钥标识符,它可能“超过500K +代”(我的需求并不需要更复杂的东西)。理想情况下,我可以根据我的唯一性需求指定长度。例如,生成的长度为12的字符串可能看起来像 "AEYGF7K0DM1X"


1460


起源


谨防 生日悖论。 - pablosaraiva
即使考虑到生日悖论,如果你使用12个字母数字字符(总共62个),你仍然需要超过340亿个字符串才能达到悖论。生日悖论无论如何都不能保证碰撞,只是说它有超过50%的几率。 - NullUserException
@NullUserException 50%的成功几率(每次尝试)非常高:即使有10次尝试,成功率也是0.999。有了这个以及你可以在24小时内尝试A LOT的事实,你不需要340亿字符串来确定至少猜测其中一个。这就是为什么一些会话令牌应该真的非常长。 - Pijusn
我猜这3个单线代码非常有用.. Long.toHexString(Double.doubleToLongBits(Math.random()));  UUID.randomUUID().toString();  RandomStringUtils.randomAlphanumeric(12); - Manindar
@Pijusn我知道这是旧的,但......生日悖论中的“50%机会”是 不 “每次尝试”,“50%的可能性,在(在这种情况下)有340亿个字符串,至少存在一对重复”。你需要1.6 九月illion - 1.6e21 - 数据库中的条目,每次尝试的几率为50%。 - Walt


答案:


算法

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

履行

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

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,大概是n2/(2QX),哪里 ñ 是实际生成的标识符数, q 是字母表中不同符号的数量,和 X 是标识符的长度。这应该是一个非常小的数字,如2-50 或更少。

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

与UUID的比较

根据他们的规范,UUID并非设计为不可预测的,并且 不应该 用作会话标识符。

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

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


1398



如果你需要空间,你可以坚持下去 .replaceAll("\\d", " ");到了最后 return new BigInteger(130, random).toString(32); 行进行正则表达式交换。它用空格替换所有数字。对我来说很有用:我用这个作为前端Lorem Ipsum的替代品 - weisjohn
@weisjohn那是个好主意。您可以通过删除数字来执行与第二种方法类似的操作 symbols 并使用空格;您可以通过更改符号中的空格数来控制平均“单词”长度(更短的单词出现次数)。对于一个非常过分的假文本解决方案,您可以使用马尔可夫链! - erickson
我试过了,但有时它只有31长。 - Daniel Szalay
为什么.toString(32)而不是.toString(36)? - ejain
@ejain因为32 = 2 ^ 5;每个字符恰好代表5位,130位可以均匀分成字符。 - erickson


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

733



请注意,此解决方案仅生成具有十六进制字符的随机字符串。在某些情况下可以没问题。 - Dave
UUID类很有用。但是,它们并不像我的答案所产生的标识符那么紧凑。这可能是一个问题,例如,在URL中。取决于您的需求。 - erickson
@Ruggs - 目标是 字母数字字符串。 如何将输出扩展到任何可能的字节? - erickson
根据RFC4122使用UUID作为令牌是一个坏主意:不要认为UUID难以猜测;例如,它们不应被用作安全功能(仅仅拥有访问权限的标识符)。可预测的随机数源将加剧这种情况。 ietf.org/rfc/rfc4122.txt - Somatik
UUID.randomUUID().toString().replaceAll("-", ""); 根据要求使字符串成为字母数字。 - Numid


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

469



+1,这是生成随机字符串的最简单的解决方案 指定长度 (除了使用Commons Lang的RandomStringUtils)。 - Jonik
考虑使用 SecureRandom 而不是 Random 类。如果在服务器上生成密码,则可能容易受到计时攻击。 - foens
我还要加小写: AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 以及其他一些允许的角色。 - ACV
为什么不放 static Random rnd = new Random(); 在方法里面? - Micro
@MicroR是否有充分的理由创建 Random 每个方法调用中的对象?我不这么认为。 - Cassio Mazzochi Molin


如果您乐意使用Apache类,则可以使用 org.apache.commons.text.RandomStringGenerator (公共文本)。

例:

RandomStringGenerator randomStringGenerator =
        new RandomStringGenerator.Builder()
                .withinRange('0', 'z')
                .filteredBy(CharacterPredicates.LETTERS, CharacterPredicates.DIGITS)
                .build();
randomStringGenerator.generate(12); // toUpperCase() if you want

从公共场所朗到3.6, RandomStringUtils 已弃用。


453



刚看完了 提到的课程 的 Apache Commons Lang 3.3.1 库 - 它只是使用 java.util.Random 提供随机序列,因此它正在产生 不安全的序列。 - Yura
使用RandomStringUtils时,请确保使用SecureRandom: public static java.lang.String random(int count, int start, int end, boolean letters, boolean numbers, @Nullable char[] chars, java.util.Random random) - Ruslans Uralovs


在一行中:

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

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


90



但只有6个字母:( - Moshe Revah
它也帮了我,但只有十六进制数字:( - noquery
@Zippoxer,你可以多次连续=) - daniel.bavrin
OP的示例显示以下String作为示例 AEYGF7K0DM1X 这不是十六进制的。它让我担心人们常常误用十六进制的字母数字。它们不是同一件事。 - hfontanez
这比字符串长度的随机性要小得多 Math.random() 产生一个 double 在0和1之间,所以指数部分大部分未使用。使用 random.nextLong 随机的 long 而不是这个丑陋的黑客。 - maaartinus


您可以使用Apache库: RandomStringUtils

RandomStringUtils.randomAlphanumeric(20).toUpperCase();

87



@kamil,我查看了RandomStringUtils的源代码,它使用了一个没有参数实例化的java.util.Random实例。 java.util.Random的文档说如果没有提供种子,它会使用当前系统时间。这意味着它不能用于会话标识符/密钥,因为攻击者可以在任何给定时间轻松预测生成的会话标识符是什么。 - Inshallah
@Inshallah:你(不必要地)过度使用系统。虽然我同意它使用时间作为种子,但攻击者必须能够访问以下数据才能真正得到他想要的内容1.时间到达精确毫秒,代码播种时2.到目前为止发生的调用次数3.他自己的电话的原子性(所以电话的数量 - 到目前为止仍然相同)如果你的攻击者拥有所有这三件事,那么你手头有更大的问题...... - Ajeet Ganga
gradle依赖: compile 'commons-lang:commons-lang:2.6' - younes0
@Ajeet这不是真的。您可以从其输出中导出随机数生成器的状态。如果攻击者可以生成几千个调用来生成随机API令牌,则攻击者将能够预测所有未来的API令牌。 - Thomas Grainger
@AjeetGanga与过度工程无关。如果要创建会话ID,则需要加密伪随机生成器。使用时间作为种子的每个prng都是可预测的,并且对于不可预测的数据非常不安全。只是用 SecureRandom 你很好 - for3st


运用 美元 应该简单如下:

// "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

36



是否有可能将SecureRandom与shuffle一起使用? - iwein