Перейти к основному содержимому

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

· 6 мин. чтения

1. Введение

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

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

2. Использование Passay

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

С помощью стандартных реализаций 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;
}

Здесь мы создали пользовательскую реализацию 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);
}

Стоит отметить, что хотя Passay является открытым исходным кодом, он имеет двойную лицензию как под LGPL, так и под Apache 2 . Как и в случае любого стороннего программного обеспечения, мы должны обязательно соблюдать эти лицензии при использовании его в наших продуктах. На веб-сайте GNU есть дополнительная информация о LGPL и Java .

3. Использование генератора случайных строк

Далее давайте посмотрим на 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 является отсутствие возможности указать количество символов в каждом наборе, как в 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;
}

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

@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 для случайности. Теперь важно отметить , что это не гарантирует криптографическую безопасность .

Однако мы можем установить источник случайности с помощью 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 . Этот класс предоставляет несколько статических методов, которые мы можем использовать для постановки задачи.

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

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

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

@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 рекомендует использовать RandomStringUtils только для простых случаев использования.

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

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

public Stream<Character> 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<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;
}

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

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

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

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