Генерация безопасного случайного пароля в Java

Генерация безопасного случайного пароля в Java

1. Вступление

В этом руководстве мы рассмотрим различные методы, которые можно использовать для создания безопасного случайного пароля на Java.

В наших примерах мы будем генерировать пароли из десяти символов, каждый из которых должен содержать минимум два символа нижнего регистра, два символа верхнего регистра, две цифры и два специальных символа.

2. Используя Passay

Passay - это библиотека применения политики паролей. В частности, мы можем использовать библиотеку для генерации пароля, используя настраиваемый набор правил.

С помощью реализацийCharacterData по умолчанию мы можем сформулировать правила, необходимые для пароля. Кроме того, мы можем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;
}

Здесь мы создали собственную реализациюCharacterData для специальных символов. Это позволяет нам ограничить набор допустимых допустимых символов.

Кроме того, мы используем реализации по умолчаниюCharacterData для других наших правил.

Теперь давайте проверим наш генератор с помощью модульного теста. Например, мы можем проверить наличие двух специальных символов:

@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);
}

Стоит отметить, чтоalthough Passay is open source, it is dual licensed under both LGPL and Apache 2. Как и в случае любого стороннего программного обеспечения, мы должны обязательно соблюдать эти лицензии при использовании его в наших продуктах. На веб-сайте GNU есть дополнительная информация оthe LGPL and Java.

3. ИспользуяRandomStringGenerator

Затем давайте посмотрим наRandomStringGenerator вApache Commons Text. С помощьюRandomStringGenerator, мы можем генерировать строки Unicode, содержащие указанное количество кодовых точек.

Теперь мы создадим экземпляр генератора, используя классRandomStringGenerator.Builder. Конечно, мы также можем дополнительно манипулировать свойствами генератора.

С помощью компоновщика мы можем легко изменить стандартную реализацию случайности. Кроме того, мы также можем определить символы, которые разрешены в строке:

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

Теперь одним из ограничений использованияRandomStringGenerator является то, что онlacks the ability to specify the number of characters in each set, like in Passay.. Однако мы можем обойти это, объединив результаты нескольких наборов:

public String generateCommonTextPassword() {
    String pwString = generateRandomSpecialCharacters(2).concat(generateRandomNumbers(2))
      .concat(generateRandomAlphabet(2, true))
      .concat(generateRandomAlphabet(2, false))
      .concat(generateRandomCharacters(2));
    List 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;
}

Затем давайте проверим сгенерированный пароль, проверив строчные буквы:

@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);
}

По умолчаниюRandomStringGenerator используетThreadLocalRandom для случайности. Теперь важно упомянутьthat this does not ensure 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. ИспользуяRandomStringUtils

Другой вариант, который мы могли бы использовать, - это классRandomStringUtils вApache Commons Lang Library. Этот класс предоставляет несколько статических методов, которые мы можем использовать для постановки задачи.

Давайте посмотрим, как мы можем предоставить диапазон кодовых точек, приемлемых для пароля:

 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 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;
}

Чтобы проверить сгенерированный пароль, давайте проверим количество цифровых символов:

@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);
}

ЗдесьRandomStringUtils по умолчанию используетRandom в качестве источника случайности. Однако в библиотеке есть метод, который позволяет нам указать источник случайности:

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

Теперь мы можем обеспечить криптографическую безопасность, используя экземплярSecureRandom. Однако эту функциональность нельзя распространить на другие методы в библиотеке. Кстати,Apache advocates the usage of RandomStringUtils for simple use cases only.

5. Использование пользовательского метода утилит

Мы также можем использовать классSecureRandom для создания специального служебного класса для нашего сценария. Для начала сгенерируем строку специальных символов длиной два:

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

Также обратите внимание, что33 и45 обозначают диапазон символов Unicode. Теперь мы можем генерировать несколько потоков в соответствии с нашими требованиями. Затем мы можем объединить наборы результатов, чтобы сгенерировать необходимый пароль:

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

Теперь давайте проверим сгенерированный пароль на количество специальных символов:

@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. Заключение

В этом руководстве мы смогли сгенерировать пароли, соответствующие нашим требованиям, используя разные библиотеки.

Как всегда, образцы кода, использованные в статье, доступныover on GitHub.