-1

I've got this issue - my password needs to have a minimum of 8 chars and have at least:

  • 1 capital letter
  • 1 lowercase letter
  • 1 special character

The function works almost as intended except sometimes it doesn't fulfill the requirements of the password listed above. How do I fix my code to ensure it meets the requirements every time it gets generated?

public class PasswordGenerator {

private static final String CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz";
private static final String NUMBERS = "0123456789";
private static final String SPECIAL_CHARACTERS = "!@#$%^&*_=+-/.?<>)";

private static final String PASSWORD_BASE = CAPITAL_LETTERS + LOWERCASE_LETTERS + NUMBERS + SPECIAL_CHARACTERS;

public static String generateRandomPassword() {
    Random random = new Random();
    int randomPasswordLength = 8 + random.nextInt(7);
    System.out.println("random password length: " + randomPasswordLength);

    char[] generatedPassword = new char[randomPasswordLength];

    for(int i=0; i<randomPasswordLength; i++) {
        generatedPassword[i] = PASSWORD_BASE.charAt(random.nextInt(PASSWORD_BASE.length()));
    }


    return new String(generatedPassword);
}
Mr.Yellow
  • 692
  • 5
  • 15
javamat
  • 119
  • 1
  • 1
  • 7
  • The obvious answer is not to mash them up into one `PASSWORD_BASE` but choose from each of your subsets separately. How you do that depends on you though – MC10 Mar 11 '19 at 20:14
  • Besides what @MC10 said, you can also add a random between these 4 groups in order to have a more randomized solution/password. – alexandrum Mar 11 '19 at 20:15
  • 1
    @alexandrum That's a good point. I should add, the reason I say it depends on you is because if you hard code it to have a capital letter at the first index and a number at the second index, then that reduces the bits of entropy and makes your password weaker. You'll want to use a method that doesn't give away this information – MC10 Mar 11 '19 at 20:18

2 Answers2

2

One way to do it is to always generate these special characters up front and then finish the password randomly. Then shuffle the char[] at the end to ensure that values are less predictable and don't start with the same symbol groups:

private static final String CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz";
private static final String NUMBERS = "0123456789";
private static final String SPECIAL_CHARACTERS = "!@#$%^&*_=+-/.?<>)";
private static final String ALL_CHARACTERS = CAPITAL_LETTERS + LOWERCASE_LETTERS + NUMBERS + SPECIAL_CHARACTERS;
private static final Random RAND = new Random();

public static char[] generateRandomPassword() {
  int length = 8 + RAND.nextInt(7);
  char[] value = new char[length];
  value[0] = randomChar(CAPITAL_LETTERS);
  value[1] = randomChar(LOWERCASE_LETTERS);
  value[2] = randomChar(NUMBERS);
  value[3] = randomChar(SPECIAL_CHARACTERS);
  for (int i = 4; i < length; i++) {
    value[i] = randomChar(ALL_CHARACTERS);
  }
  shuffle(value);
  return value;
}

private static char randomChar(String str) {
  return str.charAt(RAND.nextInt(str.length()));
}

private static void shuffle(char[] array) {
  int index;
  char temp;
  for (int i = array.length - 1; i > 0; i--) {
    index = RAND.nextInt(i + 1);
    temp = array[index];
    array[index] = array[i];
    array[i] = temp;
  }
}

The extra code for shuffling is based on this answer.

Don't store the password as String, it's less safe than char[].

Karol Dowbecki
  • 43,645
  • 9
  • 78
  • 111
1

So basically what you are doing is adding all your possible characters into one long string PASSWORD_BASE. Then you pull 8-12 characters randomly from this string and put these together to make you password. Your requirements will essentially only be fulfilled by pure luck. A possible password could be aaaaaaaa.

A possible easy fix without interrupting the random nature of your password could be a check for this conditions and if they are not fulfilled we just generate another one until we have one we like. The method could look something like this:

public static String generateRandomPassword() {
    String generatedPassword;
    do {
        Random random = new Random();
        int randomPasswordLength = 8 + random.nextInt(7);
        System.out.println("random password length: " + randomPasswordLength);

        generatedPassword = "";

        for(int i=0; i<randomPasswordLength; i++) {
            generatedPassword += PASSWORD_BASE.charAt(random.nextInt(PASSWORD_BASE.length()));
        }
    } while (hasNoCommonElements(generatedPassword, CAPITAL_LETTERS) && 
             hasNoCommonElements(generatedPassword, NUMBERS) && 
             hasNoCommonElements(generatedPassword, SPECIAL_CHARACTERS));



    return new String(generatedPassword);
}

private static boolean hasNoCommonElements(String a, String b){
    for (char c : a.toCharArray()) {
        if(b.contains("" + c))
            return false;
    }
    return true;
}

Note that this implementation has a potentially arbitrary run time until it finds a password that fulfills these requirements. Also note that this is by far not the best way to generate passwords. But I hope this will help you understand what you are doing.

Mr.Yellow
  • 692
  • 5
  • 15