Generate a Secure Random Password in Java – 在Java中生成一个安全的随机密码

最后修改: 2018年 11月 7日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this tutorial, we’ll look at various methods we can use to generate a secure random password in Java.

在本教程中,我们将研究在Java中生成安全随机密码的各种方法。

In our examples, we’ll be generating ten-character passwords, each with a minimum of two lower case characters, two uppercase characters, two digits, and two special characters.

在我们的例子中,我们将生成十个字符的密码,每个密码至少有两个小写字母,两个大写字母,两个数字和两个特殊字符。

2. Using Passay

2.使用Passay

Passay is a password policy enforcement library. Notably, we can make use of the library to generate the password using a configurable ruleset.

Passay是一个密码策略执行库。值得注意的是,我们可以利用该库,使用可配置的规则集生成密码。

With the help of the default CharacterData implementations, we can formulate the rules required for the password. Furthermore, we can formulate custom CharacterData implementations to suit our requirements:

在默认CharacterData实现的帮助下,我们可以制定密码所需的规则。此外,我们可以制定自定义的CharacterData实现,以满足我们的要求

public String generatePassayPassword() {
    PasswordGenerator gen = new PasswordGenerator();
    CharacterData lowerCaseChars = EnglishCharacterData.LowerCase;
    CharacterRule lowerCaseRule = new CharacterRule(lowerCaseChars);
    lowerCaseRule.setNumberOfCharacters(2);

    CharacterData upperCaseChars = EnglishCharacterData.UpperCase;
    CharacterRule upperCaseRule = new CharacterRule(upperCaseChars);
    upperCaseRule.setNumberOfCharacters(2);

    CharacterData digitChars = EnglishCharacterData.Digit;
    CharacterRule digitRule = new CharacterRule(digitChars);
    digitRule.setNumberOfCharacters(2);

    CharacterData specialChars = new CharacterData() {
        public String getErrorCode() {
            return ERROR_CODE;
        }

        public String getCharacters() {
            return "!@#$%^&*()_+";
        }
    };
    CharacterRule splCharRule = new CharacterRule(specialChars);
    splCharRule.setNumberOfCharacters(2);

    String password = gen.generatePassword(10, splCharRule, lowerCaseRule, 
      upperCaseRule, digitRule);
    return password;
}

Here, we’ve created a custom CharacterData implementation for special characters. This allows us to restrict the set of valid characters allowed.

这里,我们为特殊字符创建了一个自定义的CharacterData实现。这使我们能够限制允许的有效字符集。

Apart from that, we’re making use of default implementations of CharacterData for our other rules.

除此以外,我们在其他规则中使用了CharacterData的默认实现。

Now, let’s check our generator against a unit test. For instance, we can check the presence of two special characters:

现在,让我们对照单元测试来检查我们的生成器。例如,我们可以检查是否存在两个特殊字符。

@Test
public void whenPasswordGeneratedUsingPassay_thenSuccessful() {
    RandomPasswordGenerator passGen = new RandomPasswordGenerator();
    String password = passGen.generatePassayPassword();
    int specialCharCount = 0;
    for (char c : password.toCharArray()) {
        if (c >= 33 || c <= 47) {
            specialCharCount++;
        }
    }
    assertTrue("Password validation failed in Passay", specialCharCount >= 2);
}

It’s worth noting that although Passay is open source, it is dual licensed under both LGPL and Apache 2. As with any third-party software, we must be sure to comply with these licenses when we use it in our products. The GNU website has more information about the LGPL and Java.

值得注意的是,虽然Passay是开源的,但它是在LGPL和Apache 2下的双重许可。与任何第三方软件一样,当我们在产品中使用它时,我们必须确保遵守这些许可证的规定。GNU网站有关于LGPL和Java的更多信息。

3. Using RandomStringGenerator

3.使用RandomStringGenerator

Next, let’s look at the RandomStringGenerator in Apache Commons Text. With RandomStringGenerator, we can generate Unicode strings containing the specified number of code points.

接下来,让我们看看Apache Commons Text中的RandomStringGenerator。通过RandomStringGenerator,我们可以生成包含指定码位数的Unicode字符串。

Now, we’ll create an instance of the generator by using the RandomStringGenerator.Builder class. Of course, we can also further manipulate the properties of the generator.

现在,我们将通过使用RandomStringGenerator.Builder类创建一个生成器的实例。当然,我们也可以进一步操作生成器的属性。

With the help of the builder, we can easily change the default implementation of randomness. Moreover, we can also define the characters that are allowed in the string:

在构建器的帮助下,我们可以轻松地改变随机性的默认实现。此外,我们还可以定义字符串中允许的字符。

public String generateRandomSpecialCharacters(int length) {
    RandomStringGenerator pwdGenerator = new RandomStringGenerator.Builder().withinRange(33, 45)
        .build();
    return pwdGenerator.generate(length);
}

Now, one limitation of using RandomStringGenerator is that it lacks the ability to specify the number of characters in each set, like in Passay. However, we can circumvent that by merging the results of multiple sets:

现在,使用RandomStringGenerator的一个限制是,它缺乏指定每个集合的字符数的能力,就像在Passay.中一样。然而,我们可以通过合并多个集合的结果来规避这个问题。

public String generateCommonTextPassword() {
    String pwString = generateRandomSpecialCharacters(2).concat(generateRandomNumbers(2))
      .concat(generateRandomAlphabet(2, true))
      .concat(generateRandomAlphabet(2, false))
      .concat(generateRandomCharacters(2));
    List<Character> pwChars = pwString.chars()
      .mapToObj(data -> (char) data)
      .collect(Collectors.toList());
    Collections.shuffle(pwChars);
    String password = pwChars.stream()
      .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
      .toString();
    return password;
}

Next, let’s validate the generated password by verifying the lowercase letters:

接下来,让我们通过验证小写字母来验证生成的密码。

@Test
public void whenPasswordGeneratedUsingCommonsText_thenSuccessful() {
    RandomPasswordGenerator passGen = new RandomPasswordGenerator();
    String password = passGen.generateCommonTextPassword();
    int lowerCaseCount = 0;
    for (char c : password.toCharArray()) {
        if (c >= 97 || c <= 122) {
            lowerCaseCount++;
        }
    }
    assertTrue("Password validation failed in commons-text ", lowerCaseCount >= 2);
}

By default, RandomStringGenerator makes use of ThreadLocalRandom for randomness. Now, it’s important to mention that this does not ensure cryptographic security.

默认情况下,RandomStringGenerator使用ThreadLocalRandom进行随机性。现在,重要的是要提到这并不能确保加密的安全性

However, we can set the source of randomness using usingRandom(TextRandomProvider). For instance, we can make use of SecureTextRandomProvider for cryptographic security:

然而,我们可以使用usingRandom(TextRandomProvider)来设置随机性的来源。例如,我们可以利用SecureTextRandomProvider来实现加密安全。

public String generateRandomSpecialCharacters(int length) {
    SecureTextRandomProvider stp = new SecureTextRandomProvider();
    RandomStringGenerator pwdGenerator = new RandomStringGenerator.Builder()
      .withinRange(33, 45)
      .usingRandom(stp)
      .build();
    return pwdGenerator.generate(length);
}

4. Using RandomStringUtils

4.使用RandomStringUtils

Another option that we could employ is the RandomStringUtils class in the Apache Commons Lang Library. This class exposes several static methods that we can use for our problem statement.

我们可以采用的另一个选择是Apache Commons Lang Library中的RandomStringUtils类。这个类暴露了几个静态方法,我们可以将其用于我们的问题陈述。

Let’s see how we can provide the range of code points that are acceptable for the password:

让我们看看如何提供密码可接受的代码点的范围。

 public String generateCommonLangPassword() {
    String upperCaseLetters = RandomStringUtils.random(2, 65, 90, true, true);
    String lowerCaseLetters = RandomStringUtils.random(2, 97, 122, true, true);
    String numbers = RandomStringUtils.randomNumeric(2);
    String specialChar = RandomStringUtils.random(2, 33, 47, false, false);
    String totalChars = RandomStringUtils.randomAlphanumeric(2);
    String combinedChars = upperCaseLetters.concat(lowerCaseLetters)
      .concat(numbers)
      .concat(specialChar)
      .concat(totalChars);
    List<Character> pwdChars = combinedChars.chars()
      .mapToObj(c -> (char) c)
      .collect(Collectors.toList());
    Collections.shuffle(pwdChars);
    String password = pwdChars.stream()
      .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
      .toString();
    return password;
}

To validate the generated password, let’s verify the number of numeric characters:

为了验证生成的密码,我们来验证数字字符的数量。

@Test
public void whenPasswordGeneratedUsingCommonsLang3_thenSuccessful() {
    RandomPasswordGenerator passGen = new RandomPasswordGenerator();
    String password = passGen.generateCommonsLang3Password();
    int numCount = 0;
    for (char c : password.toCharArray()) {
        if (c >= 48 || c <= 57) {
            numCount++;
        }
    }
    assertTrue("Password validation failed in commons-lang3", numCount >= 2);
}

Here, RandomStringUtils makes use of Random by default as the source of randomness. However, there is a method within the library that lets us specify the source of randomness:

这里,RandomStringUtils默认使用Random作为随机性的来源。然而,库中有一个方法可以让我们指定随机性的来源。

String lowerCaseLetters = RandomStringUtils.
  random(2, 97, 122, true, true, null, new SecureRandom());

Now, we could ensure cryptographic security using an instance of SecureRandom. However, this functionality cannot be extended to other methods in the library. On a side note, Apache advocates the usage of RandomStringUtils for simple use cases only.

现在,我们可以使用SecureRandom的实例来确保加密的安全性。然而,这个功能不能扩展到库中的其他方法。顺便提一下,Apache提倡只在简单的使用情况下使用RandomStringUtils

5. Using a Custom Utility Method

5.使用自定义实用方法

We can also make use of the SecureRandom class to create a custom utility class for our scenario. For starters, let’s generate a string of special characters of length two:

我们还可以利用SecureRandom类来为我们的方案创建一个自定义的实用类。对于初学者,让我们生成一个长度为2的特殊字符的字符串。

public Stream<Character> getRandomSpecialChars(int count) {
    Random random = new SecureRandom();
    IntStream specialChars = random.ints(count, 33, 45);
    return specialChars.mapToObj(data -> (char) data);
}

Also, notice that 33 and 45 denote the range of Unicode characters. Now, we can generate multiple streams as per our requirements. Then we can merge the result sets to generate the required password:

另外,注意3345表示Unicode字符的范围。现在,我们可以根据我们的要求生成多个流。然后,我们可以合并结果集来生成所需的密码。

public String generateSecureRandomPassword() {
    Stream<Character> pwdStream = Stream.concat(getRandomNumbers(2), 
      Stream.concat(getRandomSpecialChars(2), 
      Stream.concat(getRandomAlphabets(2, true), getRandomAlphabets(4, false))));
    List<Character> charList = pwdStream.collect(Collectors.toList());
    Collections.shuffle(charList);
    String password = charList.stream()
        .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
        .toString();
    return password;
}

Now, let’s validate the generated password for the number of special characters:

现在,让我们验证一下生成的密码的特殊字符数。

@Test
public void whenPasswordGeneratedUsingSecureRandom_thenSuccessful() {
    RandomPasswordGenerator passGen = new RandomPasswordGenerator();
    String password = passGen.generateSecureRandomPassword();
    int specialCharCount = 0;
    for (char c : password.toCharArray()) {
        if (c >= 33 || c <= 47) {
            specialCharCount++;
        }
    }
    assertTrue("Password validation failed in Secure Random", specialCharCount >= 2);
}

6. Conclusion

6.结语

In this tutorial, we were able to generate passwords, conforming to our requirements, using different libraries.

在本教程中,我们能够使用不同的库来生成符合我们要求的密码。

As always, the code samples used in the article are available over on GitHub.

一如既往,文章中所使用的代码样本可在GitHub上获得