Genere una contraseña aleatoria segura en Java

Parte superior de Java

Acabo de anunciar el nuevo curso Learn Spring , centrado en los fundamentos de Spring 5 y Spring Boot 2:

>> VER EL CURSO

1. Introducción

En este tutorial, veremos varios métodos que podemos usar para generar una contraseña aleatoria segura en Java.

En nuestros ejemplos, generaremos contraseñas de diez caracteres, cada una con un mínimo de dos caracteres en minúscula, dos caracteres en mayúscula, dos dígitos y dos caracteres especiales.

2. Usando Passay

Passay es una biblioteca de aplicación de políticas de contraseñas. En particular, podemos hacer uso de la biblioteca para generar la contraseña utilizando un conjunto de reglas configurable.

Con la ayuda de las implementaciones predeterminadas de CharacterData , podemos formular las reglas necesarias para la contraseña. Además, podemos formular implementaciones de CharacterData personalizadas para satisfacer nuestros requisitos :

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

Aquí, hemos creado una implementación CharacterData personalizada para caracteres especiales. Esto nos permite restringir el conjunto de caracteres válidos permitidos.

Aparte de eso, estamos haciendo uso de implementaciones predeterminadas de CharacterData para nuestras otras reglas.

Ahora, comparemos nuestro generador con una prueba unitaria. Por ejemplo, podemos comprobar la presencia de dos caracteres especiales:

@Test public void whenPasswordGeneratedUsingPassay_thenSuccessful() { RandomPasswordGenerator passGen = new RandomPasswordGenerator(); String password = passGen.generatePassayPassword(); int specialCharCount = 0; for (char c : password.toCharArray()) if (c >= 33 

Vale la pena señalar que, aunque Passay es de código abierto, tiene doble licencia tanto bajo LGPL como con Apache 2 . Al igual que con cualquier software de terceros, debemos asegurarnos de cumplir con estas licencias cuando lo usemos en nuestros productos. El sitio web de GNU tiene más información sobre LGPL y Java.

3. Uso de RandomStringGenerator

A continuación, veamos el RandomStringGenerator en Apache Commons Text. Con RandomStringGenerator, podemos generar cadenas Unicode que contienen el número especificado de puntos de código.

Ahora, crearemos una instancia del generador usando la clase RandomStringGenerator.Builder . Por supuesto, también podemos manipular más las propiedades del generador.

Con la ayuda del constructor, podemos cambiar fácilmente la implementación predeterminada de la aleatoriedad. Además, también podemos definir los caracteres que están permitidos en la cadena:

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

Ahora, una limitación del uso de RandomStringGenerator es que carece de la capacidad de especificar el número de caracteres en cada conjunto, como en Passay. Sin embargo, podemos eludir eso fusionando los resultados de varios 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 continuación, validemos la contraseña generada verificando las letras minúsculas:

@Test public void whenPasswordGeneratedUsingCommonsText_thenSuccessful() { RandomPasswordGenerator passGen = new RandomPasswordGenerator(); String password = passGen.generateCommonTextPassword(); int lowerCaseCount = 0; for (char c : password.toCharArray()) 

De forma predeterminada, RandomStringGenerator hace uso de ThreadLocalRandom para la aleatoriedad. Ahora bien, es importante mencionar que esto no garantiza la seguridad criptográfica .

Sin embargo, podemos establecer la fuente de aleatoriedad usando usingRandom (TextRandomProvider). Por ejemplo, podemos hacer uso de SecureTextRandomProvider para seguridad 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. Uso de RandomStringUtils

Otra opción que podríamos emplear es la clase RandomStringUtils en Apache Commons Lang Library. Esta clase expone varios métodos estáticos que podemos usar para el planteamiento de nuestro problema.

Veamos cómo podemos proporcionar el rango de puntos de código que son aceptables para la contraseña:

 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 la contraseña generada, verifiquemos la cantidad 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()) 

Aquí, RandomStringUtils hace uso de Random de forma predeterminada como fuente de aleatoriedad. Sin embargo, hay un método dentro de la biblioteca que nos permite especificar la fuente de aleatoriedad:

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

Ahora, podríamos garantizar la seguridad criptográfica utilizando una instancia de SecureRandom . Sin embargo, esta funcionalidad no se puede extender a otros métodos de la biblioteca. En una nota al margen, Apache aboga por el uso de RandomStringUtils solo para casos de uso simples.

5. Uso de un método de utilidad personalizado

También podemos hacer uso de la clase SecureRandom para crear una clase de utilidad personalizada para nuestro escenario. Para empezar, generemos una cadena de caracteres especiales de longitud dos:

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

Además, observe que 33 y 45 denotan el rango de caracteres Unicode. Ahora, podemos generar múltiples flujos según nuestros requisitos. Luego, podemos fusionar los conjuntos de resultados para generar la contraseña requerida:

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

Ahora, validemos la contraseña generada para el número de caracteres especiales:

@Test public void whenPasswordGeneratedUsingSecureRandom_thenSuccessful() { RandomPasswordGenerator passGen = new RandomPasswordGenerator(); String password = passGen.generateSecureRandomPassword(); int specialCharCount = 0; for (char c : password.toCharArray()) c = 2); 

6. Conclusión

En este tutorial, pudimos generar contraseñas, de acuerdo con nuestros requisitos, utilizando diferentes bibliotecas.

Como siempre, los ejemplos de código utilizados en el artículo están disponibles en GitHub.

Fondo de Java

Acabo de anunciar el nuevo curso Learn Spring , centrado en los fundamentos de Spring 5 y Spring Boot 2:

>> VER EL CURSO