0

I am using this function to generate a random password of length 11, as seen in this post:

import java.util.Random;

public class RandomPassword {
    public static void main(String[] args){
        RandomPassword r = new RandomPassword(11);
        System.out.print(r.nextString());

    }
      private static final char[] symbols;

      static {
        StringBuilder tmp = new StringBuilder();

        for (char ch = '0'; ch <= '9'; ++ch)
          tmp.append(ch);
        for (char ch = 'a'; ch <= 'z'; ++ch)
          tmp.append(ch);
        for (char ch = 'A'; ch <= 'Z'; ++ch)
              tmp.append(ch);


        symbols = tmp.toString().toCharArray();
      }   

      private final Random random = new Random();

      private final char[] buf;

      public RandomPassword(int length) {
        if (length < 1)
          throw new IllegalArgumentException("length < 1: " + length);
        buf = new char[length];
      }

      public String nextString() {
        for (int idx = 0; idx < buf.length; ++idx) 
          buf[idx] = symbols[random.nextInt(symbols.length)];
        return new String(buf);
      }
}

However I need to modify it so that I can guarantee at least one capital letter, one number and one lowercase letter. Right now there is the possibility that the password could contain all caps/lowercase/numbers and we need at least one of each to be accepted by AD. Any help is appreciated, thanks.

***I am told that it would be best to loop through in nextString() like this:

public String nextString() {
        for (int idx = 0; idx < buf.length; ++idx) 
          if(buf[buf.length%3].equals(num/lower/cap)){
            buf[idx] = symbols[random.nextInt(symbols.length)];
        }
        .
        .
        .
        return new String(buf);
      }

What do you think?

Community
  • 1
  • 1
PT_C
  • 1,178
  • 5
  • 24
  • 57
  • Why? And that's not a silly question: why are you creating an artificial restriction in terms of "which character can be used" (thus reducing the password complexity) instead of saying "it needs to be at least 10 characters long, spaces and full unicode are allowed" so that people can use full pass phrases? (because the hashing function that you run before storing the password doesn't care if it's 6 characters or 6000 characters) – Mike 'Pomax' Kamermans Sep 12 '14 at 19:22
  • Get a random int from index 0-9 (digit) then a random int from index 10-36 (lowercase) then a random int from 37-63 (uppercase). Then the rest can be really random. [You might want to check those indices to make sure they match your string you made] – takendarkk Sep 12 '14 at 19:22
  • @Mike'Pomax'Kamermans because those are the requirements for AD. If the password doesn't have one of each it won't reset the password. – PT_C Sep 12 '14 at 19:27
  • @Mike'Pomax'Kamermans -- It's pretty common for a site to require, say, at least one upper-case, one numeral, and one "special" character in a password. And some don't allow any vowels. Yes, you're right, this weakens the password, but them's the rules. – Hot Licks Sep 12 '14 at 19:33
  • Yeah but just because it's common, doesn't mean technology moved on since we came up with that nonsense. They are not "the rules", they're just an outdated tradition that shouldn't be perpetuated. – Mike 'Pomax' Kamermans Sep 12 '14 at 19:39
  • @Mike'Pomax'Kamermans - Ah, but out-of-touch executives select the people who enforce the "outdated traditions". Those are definitely the rules. – Hot Licks Sep 12 '14 at 21:39
  • I don't see @PT_C saying "I'm payed to implement this policy" anywhere, so I have every reason to believe this is his or her own code. Let's not invent overly complex situations without reason to. – Mike 'Pomax' Kamermans Sep 12 '14 at 21:41
  • I'm not writing this for chase.com. Can someone just help with the question. – PT_C Sep 15 '14 at 00:24

6 Answers6

3
  1. Create a truly random password
  2. See if the random password meets your requirements using a regular expression
  3. If not, pick a random char from the password and modify it to be a randomly chosen symbol (or number, or uppercase letter)

This would prevent loops and would also have the benefit of being truly random.

Or, if you're ok with losing some entropy you could:

  1. Create four random strings: lowercase (length: 5), uppercase (4), number (1) and symbol (1)
  2. Concatenate the strings
  3. Shuffle the string with Collections.shuffle
nostromo
  • 1,435
  • 2
  • 17
  • 23
  • 1
    Adding to this, you could on each iteration first randomly determine what subset (lower, upper, number), then randomly select out of that partition. At the end you still need to check via regular expression if your password meets requirements. – asporter Sep 12 '14 at 19:50
1

Loop over the characters in the array to ensure that each character class is contained within. Then, if it fails, just generate a new random string. This ensures that all are contained and will not fail to validate very often.

Strikeskids
  • 3,932
  • 13
  • 27
1

I would recommend a regular expresion searching for a set of predifined characters/numbers uppercase/lowercase and a error message

   public String getPassword(){
      SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "SUN");
      byte[] salt = new byte[16];
      sr.nextBytes(salt);

      return wellformedPassword(salt.toString()) ? salt.toString() : secured(salt.toString());

   }

   public String secured(String pass){
       return string1.replaceFirst(Pattern.quote("[a-z]", "Z");
   }

   public boolean wellformedPassword(String pass){
      return checkRegExp(pass, "\\d") 
              && checkRegExp(pass, "[A-Z]")
              && checkRegExp(pass, "[a-z]");
   }

   public boolean checkRegExp(String str, String pattern){
      Pattern p = Pattern.compile(pattern);
      Matcher n = p.matcher(str);
      return n.find();
   } 

SHA assure you lowercase letters and numbers, then you just need to turn, one (in my case) or more to upper.

this is a dummy approach wich can be improved. Share your code so i cant edit later my answer for anyone else.

You could get that char and turn it to upper instead of Z but i guess with this you can get started.

  • This good, but not quite what i was looking for. I was thinking more of a way to check while the password is being produced instead of producing the password, checking it, then possibly having to produce it again. – PT_C Sep 15 '14 at 14:13
  • Using just part of the code i provided you could achieve such behavior. I added a main function wich creates a new password but making use of those functions just a mode of example. You could use just welformedPassword to check wheter the password the user provided it is or its not as you espected. Hope its helps. I belive this code its much more robust that a simple switch of if else structure. – Santiago Nicolas Roca Sep 15 '14 at 19:11
  • The password is generated when the user's account is approved, they do not create their passwords. My code ensures the correct password is generated every time without having to create-check-delete-create and check again. – PT_C Sep 16 '14 at 13:01
  • Then my code works for your propose. You should carefully read it and understand whats going on. Youll never have to create it again. When i create a sha hash im providing numbers and lower case letters. Lenght of your choice. Then i just take a random character, ensuring that character its a letter and make it uppercase. The ammount of uppercase its up to you. This will create the passwork in a single getPassword() call. The answer that you selected as correct works as well, but thinking on speed and clearness you should give this a try. – Santiago Nicolas Roca Sep 16 '14 at 13:28
0

This "cheats" (is weaker than it could be), but it meets the usual requirements (or can be modified to meet them):

import java.util.Random;
public class Password {
    private static final String UPPER = "ABCDEFGHIJKLMNPQRSTUVWXYZ";
    private static final String LOWER = "abcdefghijklmnpqrstuvwxyz";
    private static final String NUMBER = "123456789";
    private static final String SPECIAL = "!@#$%&*+?";
    private Random randGen = new Random();
    public static void main(String[] argv) {
        Password me = new Password();
        for (int i = 0; i <= 10; i++) {
            me.printPassword();
        }
    }
    private void printPassword() {
        StringBuffer buf = new StringBuffer();
        buf.append(LOWER.charAt(Math.abs(randGen.nextInt()) % LOWER.length()));
        buf.append(LOWER.charAt(Math.abs(randGen.nextInt()) % LOWER.length()));
        buf.append(NUMBER.charAt(Math.abs(randGen.nextInt()) % NUMBER.length()));
        for (int i = 0; i <= 4; i++) {
            buf.append(LOWER.charAt(Math.abs(randGen.nextInt()) % LOWER.length()));
        }
        buf.append(UPPER.charAt(Math.abs(randGen.nextInt()) % UPPER.length()));
        buf.append(LOWER.charAt(Math.abs(randGen.nextInt()) % LOWER.length()));
        System.out.println(buf.toString());
    }
}

I had another algorithm that was better, but lost it.

(I like to print out multiple "suggestions" and then pick the one that seems easiest to remember.)

Hot Licks
  • 47,103
  • 17
  • 93
  • 151
0

I really don't know how reliable it is but a try

List<Character> listOfUpperCaseLetter = new ArrayList<Character>();
for(int i = 65;i<91;i++){
    char ch = (char)i;
    listOfUpperCaseLetter.add(ch);
}
List<Character> listOfLowerCaseLetter = new ArrayList<Character>();
for(int i = 97;i<123;i++){
    char ch = (char)i;
    listOfLowerCaseLetter.add(ch);
}
List<Integer> listOfInt = new ArrayList<Integer>();
for(int i =0;i<10;i++){
    listOfInt.add(i);
}
StringBuilder br = new StringBuilder();
Collections.shuffle(listOfLowerCaseLetter);
Collections.shuffle(listOfUpperCaseLetter);
Collections.shuffle(listOfInt);
br.append(listOfUpperCaseLetter.get(0));
br.append(listOfLowerCaseLetter.get(1));
br.append(listOfLowerCaseLetter.get(3));
br.append(listOfInt.get(1));
br.append(listOfInt.get(0));
br.append(listOfLowerCaseLetter.get(2));
br.append(listOfUpperCaseLetter.get(1));
br.append(listOfLowerCaseLetter.get(0));
br.append(listOfUpperCaseLetter.get(2));
br.append(listOfInt.get(2));

System.out.println(br.toString());
SparkOn
  • 8,806
  • 4
  • 29
  • 34
0

I simply added an if/else statement saying to add certain members of the symbols array (0-10, 11-35, 36-61). This guarantees what is needed. Sometimes the simplest answer is the best answer. Thanks for the ideas.

          if (idx == 0){
            buf[idx] = symbols[random.nextInt(10)]; 
          }else if (idx == 1){
            buf[idx] = symbols[10 + random.nextInt(35-10+1)];
          }else if(idx == 2){
            buf[idx] = symbols[36 + random.nextInt(61-36+1)];
          }else{
            buf[idx] = symbols[random.nextInt(symbols.length)];
          }
PT_C
  • 1,178
  • 5
  • 24
  • 57