Gere uma senha aleatória segura em Java

Gere uma senha aleatória segura em Java

1. Introdução

Neste tutorial, veremos vários métodos que podemos usar para gerar uma senha aleatória segura em Java.

Em nossos exemplos, geraremos senhas de dez caracteres, cada uma com um mínimo de dois caracteres minúsculos, dois caracteres maiúsculos, dois dígitos e dois caracteres especiais.

2. Usando Passay

Passay é uma biblioteca de aplicação de política de senha. Notavelmente, podemos usar a biblioteca para gerar a senha usando um conjunto de regras configurável.

Com a ajuda das implementações padrão deCharacterData, podemos formular as regras necessárias para a senha. Além disso, podemosformulate 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;
}

Aqui, criamos uma implementaçãoCharacterData personalizada para caracteres especiais. Isso nos permite restringir o conjunto de caracteres válidos permitido.

Além disso, estamos usando implementações padrão deCharacterData para nossas outras regras.

Agora, vamos verificar nosso gerador em um teste de unidade. Por exemplo, podemos verificar a presença de dois caracteres especiais:

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

É importante notar quealthough Passay is open source, it is dual licensed under both LGPL and Apache 2. Como em qualquer software de terceiros, devemos garantir o cumprimento dessas licenças quando o usarmos em nossos produtos. O site GNU tem mais informações sobrethe LGPL and Java.

3. UsandoRandomStringGenerator

A seguir, vamos olhar paraRandomStringGenerator emApache Commons Text. ComRandomStringGenerator,, podemos gerar strings Unicode contendo o número especificado de pontos de código.

Agora, vamos criar uma instância do gerador usando a classeRandomStringGenerator.Builder. Obviamente, também podemos manipular ainda mais as propriedades do gerador.

Com a ajuda do construtor, podemos alterar facilmente a implementação padrão da aleatoriedade. Além disso, também podemos definir os caracteres permitidos na string:

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

Agora, uma limitação de usarRandomStringGenerator é que elelacks the ability to specify the number of characters in each set, like in Passay.. No entanto, podemos contornar isso mesclando os resultados de vários conjuntos:

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

A seguir, vamos validar a senha gerada verificando as letras minúsculas:

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

Por padrão,RandomStringGenerator usaThreadLocalRandom para aleatoriedade. Agora, é importante mencionarthat this does not ensure cryptographic security.

No entanto, podemos definir a fonte de aleatoriedade usandousingRandom(TextRandomProvider).. Por exemplo, podemos usarSecureTextRandomProvider para segurança criptográfica:

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. UsandoRandomStringUtils

Outra opção que podemos empregar é a classeRandomStringUtils emApache Commons Lang Library. Essa classe expõe vários métodos estáticos que podemos usar para a declaração do problema.

Vejamos como podemos fornecer a variedade de pontos de código aceitáveis ​​para a senha:

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

Para validar a senha gerada, vamos verificar o número de caracteres numéricos:

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

Aqui,RandomStringUtils usaRandom por padrão como a fonte de aleatoriedade. No entanto, existe um método dentro da biblioteca que permite especificar a fonte da aleatoriedade:

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

Agora, podemos garantir a segurança criptográfica usando uma instância deSecureRandom. No entanto, essa funcionalidade não pode ser estendida para outros métodos na biblioteca. Em uma nota lateral,Apache advocates the usage of RandomStringUtils for simple use cases only.

5. Usando um método utilitário personalizado

Também podemos usar a classeSecureRandom para criar uma classe de utilitário personalizado para nosso cenário. Para começar, vamos gerar uma string de caracteres especiais de comprimento dois:

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

Além disso, observe que33e45 denotam o intervalo de caracteres Unicode. Agora, podemos gerar vários fluxos de acordo com nossos requisitos. Em seguida, podemos mesclar os conjuntos de resultados para gerar a senha necessária:

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

Agora, vamos validar a senha gerada para o número de caracteres especiais:

@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. Conclusão

Neste tutorial, conseguimos gerar senhas, em conformidade com nossos requisitos, usando diferentes bibliotecas.

Como sempre, os exemplos de código usados ​​no artigo estão disponíveisover on GitHub.