【问题标题】:How to generate a secure random alphanumeric string in Java efficiently?如何有效地在 Java 中生成安全的随机字母数字字符串?
【发布时间】:2011-10-30 00:17:08
【问题描述】:

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

【问题讨论】:

  • 嗯,您能定义一下安全和随机的含义吗?例如,伪随机是否足够好?还是您需要加密强度真正的随机性?
  • 伪随机就足够了。
  • 这不是重复的,因为另一个问题不是针对安全字符串的。一个安全的答案还不够快。
  • 您需要将字符串限制为仅大写还是小写?你接受破折号吗?您是否有特定的长度要求,任何固定长度就足够了,还是它们也必须是随机长度?最后,定义“快”有多快?基本的RandomSecureRandom 解决方案都需要n 次调用nextInt()

标签: java algorithm security performance


【解决方案1】:

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

【讨论】:

  • +1 是一种将生成的字符串限制为定义的字符列表的好方法。
【解决方案2】:

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

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

【讨论】:

【解决方案3】:

这是我在the duplicate question.的代码稍作修改的版本

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 - 初始化 SecureRandom 实例很慢。事实上,如果它在您的系统上正确实施,可能需要很长时间才能为种子收集足够的熵。确保在测试中不计算初始化时间。或者,如果您不需要加密质量,请使用不同的 RNG。
  • @Erickson 我知道您的评论很旧,但是在测试时,它非常快。在eclipse中,我第二次点击运行,它打印出我的字符串。这是否意味着有问题?还是 2 年后这么快?
  • @MichaelScott 这取决于底层操作系统,以及 Java 安全属性中的“熵收集设备”设置。在 Linux 上,操作系统提供了两种设备,Java 可以读取这些设备以获取新的 SecureRandom 实例的随机种子。其中一个设备只返回随机位(通过定时各种系统事件缓慢生成),如果您在系统上消耗大量熵,它很快就会阻塞,并且需要一段时间才能创建新实例。另一个设备使用伪随机位并且是非阻塞的。有了这个,您不会看到任何延迟。
【解决方案4】:
    String chrs = "0123456789abcdefghijklmnopqrstuvwxyz-_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    SecureRandom secureRandom = SecureRandom.getInstanceStrong();
    // 9 is the length of the string you want
    String customTag = secureRandom.ints(9, 0, chrs.length()).mapToObj(i -> chrs.charAt(i))
      .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();
    System.out.println(customTag);

例子:

// q3HX6EctP
// WjRrMjQT4
// sX-Piq4DB

【讨论】:

    【解决方案5】:
    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();
        
        }
    
    }
    

    【讨论】:

    • 非常适合为 CSP 标头生成随机随机数!完成后只需对它进行 base64 编码:)
    • //minimum length of 6 \ if (length &lt; 4) { ... }: 条件与评论不符。
    • 谢谢 - 修复了代码。
    【解决方案6】:

    使用UUIDs

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

    【讨论】:

    • UUID 的随机性足以应对意外碰撞。但是,它们不能用于安全性,它们无法对抗积极尝试猜测值的攻击者。
    • 长度不是 UUID/GUID 的问题。使用“加密强 RNG”生成的 128 位很好(例如 SecureRandom)。问题是“随机”必须是加密随机的。 GUIDS 可以使用多种算法生成,并且不能保证在密码学上是安全的。例如:stackoverflow.com/questions/3652944/…
    • 即使是定义 UUID 的 RFC 也说“不要假设 UUID 很难猜测;它们不应该用作安全功能”tools.ietf.org/html/rfc4122
    • 这是一个很好的答案。 Java 的 UUID 使用 java.security.SecureRandom 和 java.security.provider.SecureRandom ……这里的可预测性被大大夸大了。一些 JVM 的 impl 可能不如 Sun/Oracle OOTB 的...
    • UUID.randomUUID() 的文档指定它使用加密性强的伪随机数生成器。
    【解决方案7】:

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

    【讨论】:

      【解决方案8】:

      我对这个随机生成的字符串的规范是生成一个 35 个字符长度的加密密钥(a-zA-Z2-7 个字符仅用于前 25 个字符)。在每 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'
      

      【讨论】:

        【解决方案9】:

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

        【讨论】:

        • 您能否编辑您的答案并添加一个代码示例来实现您的建议?
        【解决方案10】:

        http://download.oracle.com/javase/6/docs/api/java/security/SecureRandom.html

        来自 Javadoc:

        SecureRandom random = new SecureRandom();
        byte bytes[] = new byte[20];
        random.nextBytes(bytes);
        

        【讨论】:

          猜你喜欢
          • 2012-12-29
          • 2021-04-05
          • 2013-08-06
          • 1970-01-01
          • 1970-01-01
          • 2010-09-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多