Verificar se a senha tem pelo menos um tipo de caracter

Boa noite a todos, estou desenvolvendo um método que recebe um valor inteiro que será o tamanho da senha e retorna uma senha com letras maiúsculas, minúsculas, números e caracteres especiais, até aí tudo bem o problema é que percebi que pode ocorrer do método gerar senhas sem ter pelo menos um caracter de cada tipo, ou seja pode ocorrer do método gerar uma senha sem números, ou sem caracter especial, ou sem letras maiúsculas e minúsculas ou ambas. e não estou conseguindo fazer esta verificação, consegui criar um trecho no código que verifica se a senha tem todos os caracteres, mas não estou conseguindo fazer com que o método só retorne senhas válidas(todos os caracteres). Segue o código:

import java.util.Random;

public class GeradorSenha {

	/**
	 * autor Alexsandro Lopes
	 */
	public static void main(String[] args) {

		String senha = gerarSenha(8);

		System.out.println(senha);

	}

	private static String gerarSenha(int tamanho) {

		String senha = "";
		boolean senhaOK = false;

		String[] letrasL = { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p",
				"a", "s", "d", "f", "g", "h", "j", "k", "l", "ç", "z", "x",
				"c", "v", "b", "n", "m" };

		String[] letrasU = { "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P",
				"A", "S", "D", "F", "G", "H", "J", "K", "L", "Ç", "Z", "X",
				"C", "V", "B", "N", "M" };

		String[] numeros = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };

		String[] caracteres = { "!", "@", "_", "$", "%", "&", "*", ".", "/",
				"#", "?" };

		do {

			int r = new Random().nextInt(4) + 1;

			switch (r) {

			case 1:

				r = new Random().nextInt(26);
				for (; r < letrasL.length;) {
					senha = senha.concat(letrasL[r]);
					break;
				}
				break;

			case 2:

				r = new Random().nextInt(26);
				for (; r < letrasU.length;) {
					senha = senha.concat(letrasU[r]);
					break;
				}
				break;

			case 3:

				r = new Random().nextInt(9);
				for (; r < numeros.length;) {
					senha = senha.concat(numeros[r]);
					break;
				}
				break;

			case 4:

				r = new Random().nextInt(10);
				for (; r < caracteres.length;) {
					senha = senha.concat(caracteres[r]);
					break;
				}
				break;
			}

			// verifica se há pelo menos 1 caracter de cada tipo
			for (int i = 0; i < letrasL.length; i++) {

				for (int j = 0; j < letrasU.length; j++) {

					for (int l = 0; l < numeros.length; l++) {

						for (int k = 0; k < caracteres.length; k++) {

							if (senha.contains(letrasL[i])
									&& senha.contains(letrasU[j])
									&& senha.contains(numeros[l])
									&& senha.contains(caracteres[k])) {
								senhaOK = true;
							}

						}

					}

				}

			}

		} while (senha.length() < tamanho);

		// Esta saída testa se há pelo menos um caracteres  de cada tipo na senha
		System.out.println(senhaOK);
		return senha;

	}

no código a saída da variável booleana “senhaOK” pode ser true ou false.
o correto era ser sempre true e usá-la para o método retornar uma senha válida.
aguardo ajuda.

Seu problema está neste trecho.

int r = new Random().nextInt(4) + 1; 

Ele sempre irá gerar numeros aleatorios, ate 4 certo ? SO QUE, nem sempre ele vai usar todos os numeros, ou seja, as vezes ele vai repetir numeros, o que vai ocasionar que na formacao da sua senha , pelo case, ele nao ira entrar em certos pontos, e por isso nao ira gerar a senha com todos os caracteres que voce deseja.

Eu ate tentei bolar uma forma de te ajudar aqui, e consegui, so que fiz na pressa, so pra tentar enteder o que voce queria, e acho que pode ser perigoso, pois ira demorar o tempo que o processador gerar os numeros, ou seja, ninguem sabe ^^, mas funcionou, hehehe…tenta bolar uma ideia ae, partindo do que lhe falei logo la acima.

rof20004 está correto. Ainda complemento que essa coisa de ser completamente randomica as vezes não é necessário. Por exemplo, o que acontece se você passar uma senha cujo tamanho é 2? O algoritmo em questão não iria contemplar essa regra… Então partindo do principio, você precisa de pelo menos 4 caracteres para alcançar essa regra de ter uma letra (maiúscula, minúscula), carácter especial, e número. Então, nesse menor caso, você precisa informar pelo menos uma vez cada carácter. Para casos com mais digitos, você pode imaginar uma coisa em inspirai.

Concordo com vocês, luksrn, faltou eu tratar o tamanho mínino para a senha, pois sabemos que uma senha com uma certa segurança tem no mínimo 8 caracteres então esta questão de senha com menos de 4 algarismo dá pra resolver, o que eu estava querendo é quer quando fosse gerada uma senha não válida o código voltasse para o bloco do{} para gerar outra senha. para isso criei a variável senhaOK que verifica se a senha é válida ou não, o objetivo é colocar a variável senhaOK dentro do while:

} while(senha.length() < tamanho || !senhaOK);

a condição acima foi a mais próxima do que estou querendo, só retorna senhas válidas(pelo menos um caracter de cada tipo), o problema é que não respeita o tamanho da senha, é gerada senhas de vários tamanhos.

while(senha.length() < tamanho || !senhaOK);

O laço vai permanecer enquanto uma das condicoes for verdadeira, ou seja, se analisarmos, se a senha nao for OK, se ela nao for valida, o laço vai se repetir, logo, a string senha vai continuar com valor anterior, acho que voce poderia dar um reset na senha caso ela nao fosse ok, acrescentando um

senha = "";

Se você for pensar na probabilidade, que é o que você pode utilizar pra verificar uma amostra aleatória, você verá que tendo 4 listas, cada lista tem que preencher em média 25% da sua senha, por exemplo:

Sua senha tem 8 caracteres, então 2 deles seriam letras minúsculas, 2 seriam maiúsculas, 2 seriam números e 2 seriam caracteres especiais. Isso é o que a probabilidade diz.

Então por que não montar algo que garanta uma amostra parecida com essa?

No primeiro caractere, você utiliza qualquer um dos 4.
No segundo caractere, você remove o tipo que já foi usado, e escolhe um dos 3
No terceiro caractere, escolhe um dos 2 remanescentes
E no quarto, você usa o último tipo que restou.

Quando chegar no quinto caractere, você novamente escolhe qualquer um dos 4.

Fez sentido?

Excelentes dicas rof20004 e Rodrigo Sasaki vou tentar implementar.

de certa forma consegui resolver o problema: gerar senha válida com o tamanho passado por parâmetro, mas o algoritmo ficou pesado demais! :cry:
quando executado repeditamente, demora muito nas respostas, geralmente nem a resposta aparece no console depois de executar!
consome muito recurso principalmente CPU.
segue a condição:


  } while (senha.length() != tamanho || !senhaOK);  

Obrigado a todos.

Bom, eu fiz uma implementação porque gostei da brincadeira, e vou disponibilizar porque já está pronta mesmo, não tem pra que guardar.

Veja se fica melhor assim, e veja se é cabível pro seu cenário. [code]public class RandomPasswordGenerator{

private static String[] lowerCase = { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a", "s", "d", "f", "g", "h", "j",
		"k", "l", "ç", "z", "x", "c", "v", "b", "n", "m" };

private static String[] upperCase = { "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "A", "S", "D", "F", "G", "H", "J",
		"K", "L", "Ç", "Z", "X", "C", "V", "B", "N", "M" };

private static String[] numbers = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };

private static String[] special = { "!", "@", "_", "$", "%", "&", "*", ".", "/", "#", "?" };

private static LinkedList<String[]> list = new LinkedList<String[]>();

public static String generatePassword(int length){
	StringBuilder sb = new StringBuilder();
	for (int i = 0; i < length; i++){
		sb.append(getRandomCharacter());
	}
	return sb.toString();
}

private static String getRandomCharacter(){
	if (list.isEmpty()){
		fillListAndShuffle();
	}
	String[] arr = list.poll();
	return arr[getRandomIndex(arr)];
}

private static void fillListAndShuffle(){
	list.offer(lowerCase);
	list.offer(upperCase);
	list.offer(numbers);
	list.offer(special);
	Collections.shuffle(list);
}

private static int getRandomIndex(String[] arr){
	return (int) (Math.random() * arr.length);
}

}[/code] E para utilizar é só fazer assim: String password = RandomPasswordGenerator.generatePassword(8); //8 sendo o número de caracteres da senha

Quando for usar java.util.Random, não fique instanciando um objeto new Random() para cada vez que for pegar um número aleatório.
Isso é porque ele pega o horário atual, e se você chamar 100 vezes no mesmo milissegundo o método new Random(), ele vai começar 100 geradores com a mesma semente - ou seja, todos eles vão retornar o mesmo resultado :frowning:

Você deve instanciar apenas um gerador no começo do seu método e ir usando esse gerador.
Não sei quem inventou essa mania de ficar encadeando expressões, isso não é padrão Java.