如何在Java中高效地生成一个安全的随机字母数字字符串?

41

如何在Java中高效生成安全的随机(或伪随机)字母数字字符串?


2
你能定义一下你所说的“安全”和“随机”吗?比如说,伪随机是否足够?或者你需要具有密码学强度的真正随机性? - mikera
可能是重复的问题:如何在Java中生成随机字母数字字符串 - erickson
伪随机就足够了。 - devon
3
这不是重复的问题,因为另一个问题不涉及安全字符串。而唯一安全的答案速度不够快。 - devon
你需要将字符串限制为仅大写或小写吗?你接受破折号吗?你有特定的长度要求,任何固定长度是否足够,还是它们必须是随机长度?最后,定义“快”是多快?基本的RandomSecureRandom解决方案都需要n次调用nextInt() - Alistair A. Israel
11个回答

38

初始化一个包含所有被接受字符的数组 (CHARS_ARRAY),然后实例化一个SecureRandom实例,并重复调用nextInt(CHARS_ARRAY.length)来获取随机索引值。将每个字符追加到StringBuilder中,直到得到期望数量的字符。


3
+1 是一种很好的方法,可以将生成的字符串限制在一个定义好的字符列表中。 - Jason Wheeler
必须展示代码... - Philippe Fanaro

23

如果您使用 Apache Commons Lang,最简单的方法是:

RandomStringUtils.random(20, 0, 0, true, true, null, new SecureRandom());

1
这种方法并不十分安全,经过几次尝试后会导致高可预测性。我会选择SecureRandom或RandomStringGenerator.Builder。 - ninjaxelite
5
@ninjaxelite 为什么?他使用 SecureRandom 作为种子,随机性来自于新的 SecureRandom(),请看这里:https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/RandomStringUtils.html - Roie Beck
1
它明确被指定为非安全:警告:此类的实现依赖于Random的实例,这些实例不具备密码学安全性。 - Mikhail Ionkin
3
引用的注释是指 RandomStringUtils 使用的默认随机源,即 Random。答案提供了一个 SecureRandom 实例,通过一个明确设计和旨在覆盖默认实例的方法来提供。 - RvPr

17
final String chrs = "0123456789abcdefghijklmnopqrstuvwxyz-_ABCDEFGHIJKLMNOPQRSTUVWXYZ";

final SecureRandom secureRandom = SecureRandom.getInstanceStrong();

final String customTag = secureRandom
    .ints(9, 0, chrs.length()) // 9 is the length of the string you want
    .mapToObj(i -> chrs.charAt(i))
    .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
    .toString();

System.out.println(customTag);

示例:

// q3HX6EctP
// WjRrMjQT4
// sX-Piq4DB

7

这是我从重复的问题中稍作修改后的代码。

public final class RandomString
{

  /* Assign a string that contains the set of characters you allow. */
  private static final String symbols = "ABCDEFGJKLMNPRSTUVWXYZ0123456789"; 

  private final Random random = new SecureRandom();

  private final char[] buf;

  public RandomString(int length)
  {
    if (length < 1)
      throw new IllegalArgumentException("length < 1: " + length);
    buf = new char[length];
  }

  public String nextString()
  {
    for (int idx = 0; idx < buf.length; ++idx) 
      buf[idx] = symbols.charAt(random.nextInt(symbols.length()));
    return new String(buf);
  }

}

这不够快——可能是因为多次调用nextInt()的缘故? - devon
@Erickson 我知道你的评论已经有些年头了,但是在测试时,它非常快。在eclipse中,我点击运行后,它立即打印出我的字符串。这是否意味着有什么问题?还是现在两年后速度很快? - Michael Scott
1
@MichaelScott 这取决于底层操作系统和Java安全属性中的“熵收集设备”设置。在Linux上,操作系统提供了两个设备,Java可以读取这些设备以获取新的SecureRandom实例的随机种子。其中一个设备仅返回随机位(通过计时各种系统事件缓慢生成),如果您在系统上消耗了大量熵,则很快会被阻塞,并需要一段时间才能创建新实例。另一个设备使用伪随机位并且是非阻塞的。对于这个设备,您不会看到任何延迟。 - erickson
@Erickson 我需要这个SecureRandom来保证安全性,而不是模拟。有没有办法可以验证我正在使用的SecureRandom是否适合于安全目的?我不想要伪随机数。 - Michael Scott
@MichaelScott 这篇文章提供了关于各种配置选项的有用信息。应用程序可以从特定提供者请求具有特定算法的RNG;在Linux上的Sun提供者的情况下,行为取决于一些系统范围的配置,这对于应用程序来说很难验证。如果它没有进行具体的请求,应用程序将接收默认的RNG,并且很难评估其安全性。 - erickson
显示剩余2条评论

4
import java.security.SecureRandom;
import java.util.Random;

public class PasswordHelper {        
    
    public static String generatePassword (int length) {
    
    //minimum length of 6
    if (length < 6) {
        length = 6;
    }
    
    final char[] allAllowed = "abcdefghijklmnopqrstuvwxyzABCDEFGJKLMNPRSTUVWXYZ0123456789".toCharArray();
    
    //Use cryptographically secure random number generator
    Random random = new SecureRandom();
    
    StringBuilder password = new StringBuilder(); 
    
    for (int i = 0; i < length; i++) {
        password.append(allAllowed[random.nextInt(allAllowed.length)]);
    }
    
    return password.toString();
    
    }

}

1
非常适合用于生成CSP头部的随机nonce!完成后只需对其进行base64编码即可 :) - eliteproxy
//最小长度为6 if (length < 6) { ... }:条件与注释不符。 - BairDev
谢谢 - 代码已修复。 - F_SO_K

1

使用UUIDs

UUID random = UUID.randomUUID();
System.out.println( random );

17
UUID足够随机以避免意外碰撞,但它们不能用于安全目的,因为它们无法抵御主动尝试猜测值的攻击者。 - Tito
@Tito:什么?攻击者怎么可能猜到一个128位的值? - Jay Sullivan
4
长度并不是UUID/GUID的问题。使用“密码学强随机数生成器”(例如SecureRandom)生成的128位长度是可以的。问题在于“随机”必须是密码学上的随机。GUID可以使用多种算法生成,无法保证其具有密码学安全性。 例如:https://dev59.com/pnA65IYBdhLWcg3wvxaE - Tito
3
定义UUID的RFC甚至指出:“不要假设UUID难以猜测;它们不应该被用作安全功能”。 - Tito
4
这是一个好回答。Java的UUID使用java.security.SecureRandom和java.security.provider.SecureRandom...这里对其可预测性进行了夸大。一些JVM实现可能不像Sun/Oracle OOTB那样好... - Rondo
2
UUID.randomUUID() 的文档规定它使用了一个具有密码学强度的伪随机数生成器。 - Macil

0

我不确定效率如何(可能无法创建更有效的变体),但这个变体是简单且安全的(RandomStringUtils 不是):

    SecureRandom randomGenerator = new SecureRandom();
    byte[] randomBytes = new byte[20];
    randomGenerator.nextBytes(randomBytes);
    String randomString = new BigInteger(1, randomBytes).toString(16);

如果你想要更高效的变体,可以将randomGenerator指定为类字段或静态字段。
引用自https://dev59.com/snVD5IYBdhLWcg3wQJKT#44227131

0

初始化一个包含所有可接受字符的数组(CHARS_ARRAY),然后实例化一个SecureRandom实例,并重复调用nextInt(CHARS_ARRAY.length)以获取您字符数组中的随机索引。将每个字符附加到StringBuilder,直到获得所需数量的字符。


请您编辑您的答案并添加一个代码示例来实现您的建议吗? - sanastasiadis

0

为开放密钥加密算法生成公钥,并通过Base64算法将字节序列转换为字符串。


0

我对这个随机生成的字符串的规格是生成一个35个字符长度的加密密钥(前25个字符仅限a-zA-Z2-7字符)。每5个字符后使用-,最后5个字符必须是10_000到19_999之间的整数。

以下是我用Kotlin编写的代码。

companion object {

    /**
     * Letters lower & upper case excluding:
     *
     *   - l, o, B, O
     *
     * Numbers excluding:
     *
     *   - 0, 1, 8, 9
     * */
    val acceptedChars: CharArray
        get() = charArrayOf(
            'a', 'b', 'c', 'd', 'e',
            'f', 'g', 'h', 'i', 'j',
            'k', 'm', 'n', 'p', 'q',
            'r', 's', 't', 'u', 'v',
            'w', 'x', 'y', 'z', 'A',
            'C', 'D', 'E', 'F', 'G',
            'H', 'I', 'J', 'K', 'L',
            'M', 'N', 'P', 'Q', 'R',
            'S', 'T', 'U', 'V', 'W',
            'X', 'Y', 'Z', '2', '3',
            '4', '5', '6', '7'
        )

    val acceptedInts: CharArray
        get() = charArrayOf(
            '0', '1', '2', '3', '4',
            '5', '6', '7', '8', '9'
        )

    fun generate(): EncryptionKey {
        val random = SecureRandom()
        val stringBuilder = StringBuilder()
        for (i in 0 until 35) {
            when (i) {
                5, 11, 17, 23, 29 -> {
                    stringBuilder.append("-")
                }
                30 -> {
                    stringBuilder.append("1")
                }
                31, 32, 33, 34 -> {
                    val index = random.nextInt(acceptedInts.size)
                    stringBuilder.append(acceptedInts[index])
                }
                else -> {
                    val index = random.nextInt(acceptedChars.size)
                    stringBuilder.append(acceptedChars[index])
                }
            }
        }
        return EncryptionKey(stringBuilder.toString())
    }
}

生成的结果:

value: 'nK6UI-DWYvu-dbmhD-KPe5X-22dPT-10027', length: '35'
value: 'NIFvi-aX4GW-3xCYV-YSAVs-tASIK-15301', length: '35'
value: 'SpNkT-qxHR7-hSMkK-hVxpp-AqLFh-19409', length: '35'
value: 'bNvi2-svqX7-cfEw5-LNYDn-C2FtW-16197', length: '35'
value: 'hhjLX-KmRQU-KbHyU-CkNyD-5ASk6-14537', length: '35'
value: 'Xd2cj-braCm-FaE4E-Jvn2G-2Dv5J-12243', length: '35'
value: '7beFb-aeSe2-iHXZe-mTUHT-aEbry-17349', length: '35'
value: 'NExMa-xCAbU-VkpyS-xeEkj-QUayd-16311', length: '35'
value: '52HWN-EX7wV-csbhj-InhtU-gbV46-18606', length: '35'
value: 'n3RTZ-whpjQ-ZjW5n-tTyfR-eLDSF-14003', length: '35'
value: 'aJEks-ccKdU-KGJdh-Rz4ck-tR7Uq-12199', length: '35'
value: 'nMcUF-ctbcy-FEfq7-VJhRx-pCKej-16369', length: '35'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接