I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we’ll look at various methods we can use to generate a secure random password in 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

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

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:

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 "[email protected]#$%^&*()_+";
        }
    };
    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.

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

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.

3. Using 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.

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.

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:

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.

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

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

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.

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:

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.

5. Using a Custom Utility Method

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:

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:

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

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.

I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE LESSONS

Leave a Reply

avatar
  Subscribe  
Notify of