【问题标题】:Generate a Secure Random Password in Java with Minimum Special Character Requirements在 Java 中生成具有最低特殊字符要求的安全随机密码
【发布时间】:2015-09-24 11:10:19
【问题描述】:

如何在Java中创建满足系统长度和字符集要求的随机密码?

我必须创建一个长度为 10-14 个字符且至少包含一个大写字母、一个小写字母和一个特殊字符的随机密码。不幸的是,有些特殊字符特殊,不能使用,所以我不能只使用打印的ASCII。

本网站上的许多示例都生成了一个随机密码或会话密钥,而字符中没有足够的熵,或者在像上面给出的那样的业务环境中没有实际要求,所以我提出了更尖锐的问题以获得更好的答案.

我的字符集,标准美式键盘上的每个特殊字符,除了空格:

A-Z
a-z
0-9
~`!@#$%^&*()-_=+[{]}\|;:'",<.>/?

【问题讨论】:

标签: java security random passwords


【解决方案1】:
    public static final Character[] ALPHA_UPPER_CHARACTERS = {'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'};
        public static final Character[] ALPHA_LOWER_CHARACTERS = {'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'};
        public static final Character[] NUMERIC_CHARACTERS = {'0', '1', '2', '3', '4',
                '5', '6', '7', '8', '9'};
        public static final Character[] SPECIAL_CHARACTERS = {'@', '#',
                '$', '%', '^', '&', '*', '|', ';', ':', '?'};  
**Note: I copied char set from @summer** 
    
    
    final List<Character[]> charSets = new ArrayList<>();
        charSets.add(Constant.ALPHA_UPPER_CHARACTERS);
                charSets.add(Constant.ALPHA_LOWER_CHARACTERS);
                charSets.add(Constant.NUMERIC_CHARACTERS);
                charSets.add(Constant.SPECIAL_CHARACTERS);
        
        
        
        
         public String getFilterPassword() {
                    StringBuilder passBuilder = new StringBuilder();
                    final int charSetLen = charSets.size();
                    for (int i = 0; i < 10; i++) {
                        int randomLength = new Random().nextInt(charSetLen - 1);
                        Character[] newAlpha = charSets.get(randomLength);
                        int randomSetLen = newAlpha.length;
            
                        int randomAlphaLen = new Random().nextInt(randomSetLen - 1);
            
                        passBuilder.append(newAlpha[randomAlphaLen]);
                    }
                    return passBuilder.toString();
                }

【讨论】:

    【解决方案2】:

    我知道这是一个老问题,但也许我的解决方案会帮助遇到同样问题的人。 我正在使用 RandomStringUtils 作为密码生成器,然后我使用正则表达式检查密码是否满足条件(至少一个符号、一个大写字母、一个小写字母和一个数字和 8 个字符长),如果没有,那么我再次调用密码递归生成器,直到条件不满足。我可以说这个方法肯定不会被调用超过 3 次!

    public String generatePassword() {
        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@!#$%&";
        String password = RandomStringUtils.random( 8, characters );
    
        String regex = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@!#$%&])(?=\\S+$).{8,}$";
        Pattern pattern = Pattern.compile( regex );
        Matcher matcher = pattern.matcher( password );
    
        if (matcher.matches()) {
            return password;
        } else {
            return generatePassword(); // recursion
        }
    }
    

    Maven 依赖:

    <dependency>
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-lang3</artifactId>
       <version>3.11</version>
    </dependency>
    

    【讨论】:

      【解决方案3】:

      这是一个仅使用普通 Java 并实现要求的实用程序。它基本上获得了每个所需的字符集之一。然后用整个集合中的随机字符填充其余部分。然后随机播放。

      public class PasswordUtils {
      
          static char[] SYMBOLS = "^$*.[]{}()?-\"!@#%&/\\,><':;|_~`".toCharArray();
          static char[] LOWERCASE = "abcdefghijklmnopqrstuvwxyz".toCharArray();
          static char[] UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
          static char[] NUMBERS = "0123456789".toCharArray();
          static char[] ALL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789^$*.[]{}()?-\"!@#%&/\\,><':;|_~`".toCharArray();
          static Random rand = new SecureRandom();
      
          public static String getPassword(int length) {
              assert length >= 4;
              char[] password = new char[length];
      
              //get the requirements out of the way
              password[0] = LOWERCASE[rand.nextInt(LOWERCASE.length)];
              password[1] = UPPERCASE[rand.nextInt(UPPERCASE.length)];
              password[2] = NUMBERS[rand.nextInt(NUMBERS.length)];
              password[3] = SYMBOLS[rand.nextInt(SYMBOLS.length)];
      
              //populate rest of the password with random chars
              for (int i = 4; i < length; i++) {
                  password[i] = ALL_CHARS[rand.nextInt(ALL_CHARS.length)];
              }
      
              //shuffle it up
              for (int i = 0; i < password.length; i++) {
                  int randomPosition = rand.nextInt(password.length);
                  char temp = password[i];
                  password[i] = password[randomPosition];
                  password[randomPosition] = temp;
              }
      
              return new String(password);
          }
      
          public static void main(String[] args) {
              for (int i = 0; i < 100; i++) {
                  System.out.println(getPassword(8));
              }
          }
      }
      

      【讨论】:

        【解决方案4】:

        利用rt.jar的java.util包的随机功能,我们可以创建任意长度的随机密码。下面是相同的sn-p。

        public class GeneratePassword {
        
        public static void main(String[] args)
        {
                int length = 10;
                String symbol = "-/.^&*_!@%=+>)"; 
                String cap_letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 
                String small_letter = "abcdefghijklmnopqrstuvwxyz"; 
                String numbers = "0123456789"; 
        
        
                String finalString = cap_letter + small_letter + 
                        numbers + symbol; 
        
                Random random = new Random(); 
        
                char[] password = new char[length]; 
        
                for (int i = 0; i < length; i++) 
                { 
                    password[i] = 
                            finalString.charAt(random.nextInt(finalString.length())); 
        
                } 
                System.out.println(password);
        }
        

        }

        【讨论】:

          【解决方案5】:

          我建议使用 apache commons RandomStringUtils。使用已经完成的东西。

          String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^&*()-_=+[{]}\\|;:\'\",<.>/?";
          String pwd = RandomStringUtils.random( 15, characters );
          System.out.println( pwd );
          

          如果你使用的是 maven

          <dependency>
              <groupId>org.apache.commons</groupId>
              <artifactId>commons-lang3</artifactId>
              <version>3.4</version>
          </dependency>
          

          否则下载jar

          更新 带有安全随机的版本。所以剩下的所需字符的问题可以像注释一样解决,分别生成所需的部分和正常的部分。然后随机加入。

          char[] possibleCharacters = (new String("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^&*()-_=+[{]}\\|;:\'\",<.>/?")).toCharArray();
          String randomStr = RandomStringUtils.random( randomStrLength, 0, possibleCharacters.length-1, false, false, possibleCharacters, new SecureRandom() );
          System.out.println( randomStr );
          

          【讨论】:

          • 虽然这可能会提供满足要求的密码,但不能保证。 RandomStringUtils 有两个问题: #1 它不保证至少一个大写、一个小写、一个数字和一个特殊字符的字符要求。 #2 RandomStringUtils 在下面使用 Random(),它的密码强度不高,因此不适用于密码。
          • 然后将这个随机生成拆分为2代,以保证字符串元素的需求。在第一代中放入所需的字符,在第二代中放入其余部分或所有您想要的字符。在密码学的情况下,我不是专家,所以我不解决这个问题。
          • 对于使用此答案生成将用作 http 请求 url 或正文的一部分的字符串的人,您需要在使用之前对值进行编码/转义
          【解决方案6】:

          我最近了解到Passay。它提供了PasswordGenerator class 中所需的功能。它使用 CharacterRules 而不是 PasswordCharacterSets 随机生成满足以下要求的密码,就像我在下面所做的那样。它不是保存一个未使用的索引列表用于随机字符插入,而是在插入符合要求的字符后简单地打乱字符缓冲区。

          以下是之前遗留的,如果您的许可允许,我建议使用 Passay,否则此代码应该可以正常工作,并提供生成密码为何具有密码强度的详细信息

          我最终将这段代码写了两次。曾经得到一个随机字符结果,但事实证明字符的分布取决于字符集的大小(哎呀!)。我重写了它,现在您应该复制/粘贴代码并将 Main.java 更改为您想要的字符集。尽管它可以采用不同的方式,但我认为这是一种相对简单的方法来获得正确的结果,我鼓励重用、cmet、批评和深思熟虑的编辑。

          PasswordGenerator 代码的控件如下:

          • 最小/最大长度:使用随机数设置
          • PasswordCharacterSet:假设所有传入 PasswordGenerator 的 PasswordCharacterSet 都由唯一的字符集组成,否则,随机字符将偏向重复。
          • PasswordCharacterSet Min Characters:用于此字符集的最少字符。

          实际密码生成的主要位:

          • Random 的随机性:我们使用的是 SecureRandom,它由强大的加密 PRNG 支持,而不是不支持的 Random 类。
          • 密码的随机字符顺序:将 pw 字符数组的所有索引添加到剩余索引数组中。当我们调用 addRandomCharacters 时,它会随机删除一个索引,然后我们使用删除的索引来填充数组。
          • 随机字符:在 addRandomCharacters 中,从我们正在使用的字符索引中选择一个随机索引并将其添加到 pw 数组中。
          • 保证设置每种类型的最小字符数:我们只是先简单地划分出最小字符数。我们从每个字符集中选择最少数量的随机值,然后继续。
          • 剩余字符的随机分布:设置最小值后,我们想让剩余的字符在所有字符集中随机分布。所有字符都添加到一个数组中。剩余的插槽使用与之前字符集相同的策略填充。

          密码复杂度的描述:密码复杂度通常用熵来表示。以下是您的键空间的多种可能性:

          至少有一个大写字母字符(共 26 个)、一个小写字母字符(共 26 个)、一个数字(共 10 个)和一个特殊字符(共 32 个),您计算可能性数是每个字符的可能性数乘以字符数,因为它们是随机放置在字符串中的。所以我们知道其中四个角色的可能性是:

          Required Characters = 26*26*10*32=216,320
          

          所有剩余的字符各有 94 (26+26+10+32) 种可能性

          我们的计算是:

          Characters  Possibilities                                       Bits of Entropy
          10 chars    216,320*94^6  = 149,232,631,038,033,920             ~2^57
          11 chars    216,320*94^7  = 14,027,867,317,575,188,480          ~2^63
          12 chars    216,320*94^8  = 1,318,619,527,852,067,717,120       ~2^70
          13 chars    216,320*94^9  = 123,950,235,618,094,365,409,280     ~2^76
          14 chars    216,320*94^10 = 11,651,322,148,100,870,348,472,320  ~2^83
          

          考虑到这一点,如果您想要最安全的密码,您应该始终选择尽可能多的字符,在这种情况下为 14 个。

          Main.java

          package org.redtown.pw;
          
          import java.util.EnumSet;
          import java.util.HashSet;
          import java.util.Set;
          
          import org.redtown.pw.PasswordGenerator.PasswordCharacterSet;
          
          public class Main {
          
              public static void main(String[] args) {
                  Set<PasswordCharacterSet> values = new HashSet<PasswordCharacterSet>(EnumSet.allOf(SummerCharacterSets.class));
                  PasswordGenerator pwGenerator = new PasswordGenerator(values, 10, 14);
                  for(int i=0; i < 10; ++i) {
                      System.out.println(pwGenerator.generatePassword());
                  }
              }
          
              private static final char[] ALPHA_UPPER_CHARACTERS = { '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' };
              private static final char[] ALPHA_LOWER_CHARACTERS = { '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' };
              private static final char[] NUMERIC_CHARACTERS = { '0', '1', '2', '3', '4',
                      '5', '6', '7', '8', '9' };
              private static final char[] SPECIAL_CHARACTERS = { '~', '`', '!', '@', '#',
                      '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '[', '{',
                      ']', '}', '\\', '|', ';', ':', '\'', '"', ',', '<', '.', '>', '/',
                      '?' };
          
              private enum SummerCharacterSets implements PasswordCharacterSet {
                  ALPHA_UPPER(ALPHA_UPPER_CHARACTERS, 1),
                  ALPHA_LOWER(ALPHA_LOWER_CHARACTERS, 1),
                  NUMERIC(NUMERIC_CHARACTERS, 1),
                  SPECIAL(SPECIAL_CHARACTERS, 1);
          
                  private final char[] chars;
                  private final int minUsage;
          
                  private SummerCharacterSets(char[] chars, int minUsage) {
                      this.chars = chars;
                      this.minUsage = minUsage;
                  }
          
                  @Override
                  public char[] getCharacters() {
                      return chars;
                  }
          
                  @Override
                  public int getMinCharacters() {
                      return minUsage;
                  }
              }
          }
          

          密码生成器.java

          package org.redtown.pw;
          
          import java.security.SecureRandom;
          import java.util.ArrayList;
          import java.util.Arrays;
          import java.util.Collection;
          import java.util.Collections;
          import java.util.List;
          import java.util.Random;
          
          public class PasswordGenerator {
          
              private final List<PasswordCharacterSet> pwSets;
              private final char[] allCharacters;
              private final int minLength;
              private final int maxLength;
              private final int presetCharacterCount;
          
              public PasswordGenerator(Collection<PasswordCharacterSet> origPwSets, int minLength, int maxLength) {
                  this.minLength = minLength;
                  this.maxLength = maxLength;
          
                  // Make a copy of the character arrays and min-values so they cannot be changed after initialization
                  int pwCharacters = 0;
                  int preallocatedCharacters = 0;
                  List<PasswordCharacterSet> pwSets = new ArrayList<PasswordCharacterSet>(origPwSets.size());
                  for(PasswordCharacterSet origpwSet : origPwSets) {
                      PasswordCharacterSet newPwSet = new PwSet(origpwSet);
                      pwSets.add(newPwSet);
                      pwCharacters += newPwSet.getCharacters().length;
                      preallocatedCharacters += newPwSet.getMinCharacters();
                  }
                  this.presetCharacterCount = preallocatedCharacters;
                  this.pwSets = Collections.unmodifiableList(pwSets);
          
                  if (minLength < presetCharacterCount) {
                      throw new IllegalArgumentException("Combined minimum lengths "
                              + presetCharacterCount
                              + " are greater than the minLength of " + minLength);
                  }
          
                  // Copy all characters into single array so we can evenly access all members when accessing this array
                  char[] allChars = new char[pwCharacters];
                  int currentIndex = 0;
                  for(PasswordCharacterSet pwSet : pwSets) {
                      char[] chars = pwSet.getCharacters();
                      System.arraycopy(chars, 0, allChars, currentIndex, chars.length);
                      currentIndex += chars.length;
                  }
                  this.allCharacters = allChars;
              }
          
          
              public char[] generatePassword() {
                  SecureRandom rand = new SecureRandom();
          
                  // Set pw length to minLength <= pwLength <= maxLength
                  int pwLength = minLength + rand.nextInt(maxLength - minLength + 1);
                  int randomCharacterCount = pwLength - presetCharacterCount;
          
          
                  // Place each index in an array then remove them randomly to assign positions in the pw array
                  List<Integer> remainingIndexes = new ArrayList<Integer>(pwLength);
                  for(int i=0; i < pwLength; ++i) {
                      remainingIndexes.add(i);
                  }
          
                  // Fill pw array
                  char[] pw = new char[pwLength];
                  for(PasswordCharacterSet pwSet : pwSets) {
                      addRandomCharacters(pw, pwSet.getCharacters(), pwSet.getMinCharacters(), remainingIndexes, rand);
                  }
                  addRandomCharacters(pw, allCharacters, randomCharacterCount, remainingIndexes, rand);
                  return pw;
              }
          
              private static void addRandomCharacters(char[] pw, char[] characterSet,
                      int numCharacters, List<Integer> remainingIndexes, Random rand) {
                  for(int i=0; i < numCharacters; ++i) {
                      // Get and remove random index from the remaining indexes
                      int pwIndex = remainingIndexes.remove(rand.nextInt(remainingIndexes.size()));
          
                      // Set random character from character index to pwIndex
                      int randCharIndex = rand.nextInt(characterSet.length);
                      pw[pwIndex] = characterSet[randCharIndex];
                  }
              }
          
              public static interface PasswordCharacterSet {
                  char[] getCharacters();
                  int getMinCharacters();
              }
          
              /**
               * Defensive copy of a passed-in PasswordCharacterSet
               */
              private static final class PwSet implements PasswordCharacterSet {
                  private final char[] chars;
                  private final int minChars;
          
                  public PwSet(PasswordCharacterSet pwSet) {
                      this.minChars = pwSet.getMinCharacters();
                      char[] pwSetChars = pwSet.getCharacters();
                      // Defensive copy
                      this.chars = Arrays.copyOf(pwSetChars, pwSetChars.length);
                  }
          
                  @Override
                  public char[] getCharacters() {
                      return chars;
                  }
          
                  @Override
                  public int getMinCharacters() {
                      return minChars;
                  }
              }
          }
          

          【讨论】:

          • 我担心这个库的线程安全。我想在我的网络应用程序中使用它。我确实在某处看到了StringBuffer 的使用,但作者从未提及线程安全。
          猜你喜欢
          • 2012-03-31
          • 2021-12-02
          • 2019-09-03
          • 2017-01-15
          • 2021-06-18
          • 1970-01-01
          • 2011-12-22
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多