Java Password Generator


#1

So I'm actually writing a java password generator using ASCII chars and the Math.random() method for my AP Computer Science class. In a nutshell, I'll have a menu, 1 - 5, in which you can choose for your password to have lowercase letters, numbers, uppercase letters, and punctuation symbols respectively. (Each one has the ones before it as well.) You choose the menu option, choose the number of letters in a password, and then the program generates using a huge nested loop and I'm sure you can imagine it. I am virtually done, but my one problem is the way I'm generating each char. My lowercase letters work fine, I use the code:

for(password = ""; password.length() < lengthPass; password += randomChar) {
    randNum = (int)(Math.random() * 25) + 97;                
    randomChar = (char)randNum; 
}

This should give you an idea of how I'm running it. Anyways, this one does work, but in my next menu item, where I use numbers AND lowercase letters, what I'm printing out is not what I want. My code is:

else if (choice == 2) {
    for(password = ""; password.length() < lengthPass; password += randomChar) {           
        randNum = Math.random();
        if (randNum < 0.5) {
            randNumber = (int)(randNum * 2) * 25 + 97;
            randomChar = (char)randNum;
}
        else {
            randNum = (int)(randNum * 9) + 48;
            randomChar = (char)randNum;
}

So this looks pretty similar to the first one, and to me, there is nothing noticeably wrong. However, what my console prints are things such as:

However, when I copy and paste this, I only get "8655". This isn't really a big deal, I'm just trying to provide any information I can. Honestly, I don't know why it's happening, and I don't want to know how to fix it, but I would like to know what's wrong, or even if you can tell what's wrong. Sorry that this was so long, but I'm really struggling.


#2

Where does this variable get used? Is the generated password cast to a string?


#3

I have a lot of predefined variables. That one in particular gets used to add a character to the empty string. It's actually maybe not that good that I use it over and over, but I got to a certain place with my code, and now I'm even closer to finished. I fixed my initial problem with needing to not have zeros, I was literally just missing parenthesis and casting decimals as integers.

Code:

/* Kylea Watkins   November 3, 2016    
   This program generates passwords. */

import java.util.Scanner;
class Password {
   public static void main (String [ ] args) {

      Scanner in = new Scanner(System.in);
      
      System.out.println("              Password Generator Menu               ");
      System.out.println("****************************************************");
      System.out.println("* [1] Lowercase Letters                            *");
      System.out.println("* [2] Lowercase and Numbers                        *");
      System.out.println("* [3] Lowercase, Uppercase, & Numbers              *");
      System.out.println("* [4] Lowercase, Uppercase, Numbers, & Punctuation *"); 
      System.out.println("* [5] Quit                                         *");      
      System.out.println("****************************************************");      
      System.out.print("Enter Selection (1-5): "); 
      int choice = in.nextInt();          
      
      while (choice != 5) {
         if (choice < 1 || choice > 4) {
            System.out.println("Invalid option. Please try again.");
            
         }
         else {
            Scanner input = new Scanner(System.in);
            System.out.print("Password Length (6 or more): ");
            int lengthPass = input.nextInt(); 
            String password = "";
            char randomChar;
            double randNum = 0;
            int randNumber = 0;     
            if(lengthPass < 6) {
               System.out.println("Password length too short. Please try again");
               Scanner input1 = new Scanner(System.in);
            }   
            else { 
               if (choice == 1) {           
                  for(password = ""; password.length() < lengthPass; password += randomChar) {
                     randNum = (int)(Math.random() * 25) + 97;                
                     randomChar = (char)randNum; 
                  }           
               }        
                     
               else if (choice == 2) {
                  for(password = ""; password.length() < lengthPass; password += randomChar) {           
                     randNum = Math.random();
                     if (randNum < 0.5) {
                        randNum = (int)((randNum * 2) * 25) + 97;
                        randomChar = (char)randNum;
                     }
                     else {
                        randNum = (int)((randNum - 0.5) * 2 * 9) + 48;
                        randomChar = (char)randNum;
                     }                        
                  }
               }
               else if (choice == 3) {
                  for(password = ""; password.length() < lengthPass; password += randomChar) {                
                     randNum = Math.random();
                     if (randNum < 0.33) {
                        randNum = ((randNum * 3) * 25) + 97;              
                        randomChar = (char)randNum; 
                     }
                     else if (randNum < 0.66) {
                        randNum = (int)((randNum - 0.33) * 3 * 9) + 48;
                        randomChar = (char)randNum;
                     }
                     else {
                        randNum = (int)((randNum - 0.66) * 25) + 65;
                        randomChar = (char)randNum;
                     }        
                  }        
               }         
               else if (choice == 4) {            
                  for(password = ""; password.length() < lengthPass; password += randomChar) {
                     randNum = Math.random();
                     if(randNum < 0.25) {
                        randNum = (int)((randNum * 4) * 25) + 97;
                        randomChar = (char)randNum;
                     }
                     else if(randNum < 0.5) {   
                        randNum = (int)((randNum - 0.25) * 4 * 9) + 48;
                        randomChar = (char)randNum;
                     }
                     else if(randNum < 0.75) {
                        randNum = (int)((randNum - 0.5) * 4 * 25) + 65;
                        randomChar = (char)randNum;
                     }
                     else if(randNum < 0.87){
                        randNum = (int)((randNum - 0.75) * 4 * 4) + 91;
                        randomChar = (char)randNum; 
                     }
                     else {
                        randNum = (int)((randNum - 0.87) * 4 * 3) + 123;
                        randomChar = (char)randNum;
                     }
                  }
               }
               System.out.println("Randomly generated password: " + password);

            }
         }
         System.out.print("\nEnter Selection (1-5): ");
         choice = in.nextInt();  
      }
      System.out.println("Thank you, the Password Generator will exit now.");
   }
}

I spent 8 + hours on this, but I feel a lot better about it. :slight_smile:


#4

@kyleaw ,

This looks like a great project, and you've done a nice job with it.

However, should the 25 in this line be a 26, instead? ...

randNum = (int)(Math.random() * 25) + 97;

Math.random will give you a number between 0 and 1, but not including 1. If it gives you a 0.0, then 0 will get added to 97, and you'll get an a, so you have the lower end of the alphabet covered. However, if Math.random() gives you, say, 0.9999999, then multiplying by 25 will give you a little less than 25, and truncated to an integer, you'll get 24. Then, 24 + 97 gives you 121 which is ascii for y. It seems you can never get a z. I think you need a 26 there instead of 25, if you want to be able to include z.

You might have to adjust some other lines as well. For example, to include the digit 9, this ...

randNum = (int)((randNum - 0.33) * 3 * 9) + 48;

... might have to be changed to this ...

randNum = (int)((randNum - 0.33) * 3 * 10) + 48;

You have cleverly divided up the range from 0 to 0.9999 so that for any given choice that the user makes, different parts of the range will give a lowercase letter, and uppercase letter, a digit, or a symbol, etc.

Below is an experiment, done in Python 3.5, using another approach, that chooses among lowercase letters, uppercase letters, and digits, with the aim of giving each character an equal chance of being chosen, regardless of whether it is a lowercase letter, uppercase letter, or a digit. I'm not suggesting that you should do it this way, but thought you might want to see it.

# random_char.py
import random
def random_char():
    n_choices = 62 # number of possible different characters
    code = int(random.random() * n_choices)
    if 0 <= code <= 9: # digit
        code += 48 # add 48 to get to ascii codes of "0-9"
    elif 10 <= code <= 35: # lowercase letter
        code += 87 # add 87 to get to ascii codes of "a-z"
    else:
        code += 29 # uppercase letter; add 29 to get to ascii codes of "A-Z"
    return chr(code)

def create_passwd():
    passwd = ""
    for i in range(40):
        passwd += random_char()
    return passwd

print(create_passwd())

Output ...

Nyf9BtmX0Cb0w0hIoyTWeU1UuWzwuG4MQ2Vi4rpO

Notice the assignment ...

   n_choices = 62

Among the lowercase letters, uppercase letters, and digits, there are a total of 62 different characters. So, this line assigns an int from 0 to 61, inclusive to code ...

   code = int(random.random() * n_choices)

The lines that follow that one use the value of code to pick among the 62 possible characters.


#5

WOAH! Knowledge overload man, that's so awesome! Thank you so, so, so much for sharing this with me! Changing the 25 to a 26 is a major help, because I never would've thought of it that way. I'm going to go ahead and make the changes, that's such awesome advice. As to the python stuff, that's seriously cool. It did in 15 lines what I did in 110. Now I can definitely say that I prefer the less clunky OOP like Python, Ruby, and most of the time JS. The one thing that is terrible in this gigantic nested loops is the braces. This was definitely a tiring project. In all honestly, it's not a very difficult program, but it's the first time that I did something this in depth, and I really think it's cool even though as soon as I finished it I was exhausted beyond exhaustion, lol. Additionally, I really liked that you shared this python project with me, because it's SO simple, and it honestly has really cool detail. I'm in such shock that it's so small, and my code is sooo long. This is a huge mind blower, because I originally started coding in python, but my Computer Science class is strictly Java. I'm blown away. This really gives some perspective. Why do we even use Java if it's not as efficient? I mean I understand that there are do while loops and there is so much in Java that could've simplified my project at least a little bit, but since it's an actual class, they only really allow me to use what I've learned so far IN THE CLASS even though I've learned much more expansive things in other languages. :slight_smile: You're my hero right now!
I notice that the one huge difference though is that mine uses user input and it repeats until the user asks it not to.


#6

The main question behind this was not related to passwords, but in the process of finding a solution the password idea more or less slipped in.

This is another Python program, 2.7.x.

import string
import random

CHAR_LOOKUP = list(\
    string.digits +\
    string.ascii_uppercase +\
    string.ascii_lowercase)

def numeral(n):
    global CHAR_LOOKUP
    return CHAR_LOOKUP[n]

def convert_base(number, base):
    if base < 2 or base > 62:
        return False
    mods = []
    while number > 0:
        mods.append(numeral(number % base))
        number //= base
    mods.reverse()
    return ''.join(mods)

def getTen():
    r = convert_base(random.randrange(1e18), 62)
    return r if len(r) == 10 else getTen()

# test
for i in range(10):
    print getTen()

Output

Jo5Nt0cALc
qfqnDLXXyW
UADHDEECAz
fEvKh5oQL8
J3USujphIf
3NFxTzmqnM
q4r6IbBiHy
MhridH5yk3
UbIMthUPMY
YVPfQTKKsE

Not to prove anything. It's a whole 'nother approach that is here only scratched upon. I only chanced upon it, myself.

 > convert_base(int(1e36), 62)
=> 1Q0wbBCZxbzmSp8Hqr8KG
 > convert_base(int(1e72), 62)
=> 20wgMRBdLBgZQPmElmNwKjuNic4aV2Ls1dQQvvYnI
 > convert_base(int(1e144), 62)
=> 43nczTfw5XJQ388FBGnu6GOpnVfUHPnuC7dnFB9c7QD4dFc9Xrc2OXThJ9iP3sBPEilZ9R8nY3bWsCFnc

To create a 62 character password, we just bump up the power.

 > a = convert_base(random.randrange(1e111), 62)
 > a
=> 'ELJTKMkAQpaBDRryMEQJXQ1dqRJhDL7yAsfLCmHd1EDskQCOQHWcTDPqVwsPyV'
 > len(a)
=> 62

What's most subtle in this whole thing is that only one random number is generated.


#7

Thanks for the very cool additional approaches, @mtf :slight_smile:

@kyleaw, it's great that you are learning Java. It is rich in libraries, is used a lot, and is fast, so is good to know. There's lots it can do, so be sure to enjoy it and to get the most out of your class.

It's interesting to check out the TIOBE Index to see what programming languages people are using or looking up frequently. Java is quite popular, at present. There are other language popularity indexes in addition to this one.

Another potential approach to the password project that draws upon what @mtf has presented, is to maintain four lists as strings, namely 1) lowercase letters, 2) uppercase letters, 3) digits, and 4) symbols. The user can make her/his choice, then you concatenate whatever lists together that are appropriate into a new string (akin to @mtf's CHAR_LOOKUP string), generate a random integer based on the length of that CHAR_LOOKUP string (0 to the length of the string, exclusive), and use that number as an index to get a char from the string. That would give each char an equal chance of getting picked. However, if you wanted to weight the odds of a particular type of character's being picked, for example, favor symbols a little, you could use two calls to the random number generator to pick each char. The first one would select one of the four original strings, based on subranges within 0.0 to 0.999999, and the second call, based on the length of the selected string, would pick a char from that string.

Whatever you ultimately decide to do, your project is quite cool as it is, and there is really no need to change it at this point. Is there an instructor for the course who provides opportunities to discuss ideas with her/him, as you are working on a project, and who can inform of what approach she/he prefers that you use?


#8

Java is an extremely strong program, and it's pretty consistent, it's just not always the most fun.
Thank you for the feedback and compliment. I love to talk about things that I understand and then bring up different ways to accomplish them. Since I homeschool virtually, I submit my .java file to my instructor and he runs it in Blue J or whichever IDE he uses. He will give me feedback, but I do get to talk about the project. The other cool thing is that my dad is an electrical engineer, but he did computer science in college and we talk a lot about it so I like living with somebody that programs as well and that I can learn from.
The teacher will typically tell me what approach he wants, but the nicest thing about the class is that with every program I write, I have an expected output so I know somewhat what's needed.
Again, thanks!


#9

Good luck on your project.

One more Python version of it ...

# passwd.py
import string
import random

def display_menu():
    # Display a menu of password character types
    # Adapted from Java version of menu by @kyleaw
    print("      Password Generator Menu")
    print("****************************************************")
    print("* [1] Lowercase Letters     *")
    print("* [2] Lowercase and Numbers *")
    print("* [3] Lowercase, Uppercase, & Numbers  *")
    print("* [4] Lowercase, Uppercase, Numbers, & Punctuation *") 
    print("* [5] Quit     *")
    print("****************************************************")

def get_menu_choice():
    # Get the user's menu choice
    choice = "0"
    while choice not in "12345":
        choice = input("Enter Selection (1-5): ")
    return int(choice)

def get_passwd_len():
    # Get the length of the password
    passwd_len = 0
    while passwd_len < 6:
        try:
            passwd_len = int(input("Password length (6 or greater): "))
        except ValueError:
            print("Invalid number")
    return passwd_len
   

def get_char_from_string(source):
    # Choose a random character from a string
    return source[random.randint(0, len(source) - 1)]

def shuffle(passwd):
    # Shuffle the order of the characters
    chars = list(passwd)
    passwd = ""
    while len(chars) > 0:
        passwd += chars.pop(random.randint(0, len(chars) - 1))
    return passwd
    
def generate_password():   # Possible sources of characters for the password
    # Thanks to @mtf for idea to use Python string module
    sources = [string.ascii_lowercase, string.digits, string.ascii_uppercase, string.punctuation]
    display_menu()
    choice = get_menu_choice()
    if 1 <= choice <= 4:
        length = get_passwd_len()
        source_string = sources[0]
        # Guarantee at least one lowercase letter
        passwd = get_char_from_string(sources[0])
        for i in range(1, choice):
            source_string += sources[i]
            passwd += get_char_from_string(sources[i])
        # Guarantee at least one character from other selected sources
        for i in range(length - choice):
            passwd += get_char_from_string(source_string)
        passwd = shuffle(passwd)
        print("Your password: {:s}".format(passwd))
    else:
        print("User chose to quit.")

generate_password()
Output ...

      Password Generator Menu
****************************************************
* [1] Lowercase Letters     *
* [2] Lowercase and Numbers *
* [3] Lowercase, Uppercase, & Numbers  *
* [4] Lowercase, Uppercase, Numbers, & Punctuation *
* [5] Quit     *
****************************************************
Enter Selection (1-5): 4
Password length (6 or greater): 40
Your password: 5IfuaY7OHo}iV*o9h,4xG1<ok~xb$w^JUY}"{;&h

#10

Wow this is really good! Thank you so much for taking the time and also sharing this with me. I really do appreciate it, and I get a lot of understanding and insight out of this. I'm going to play around with this some! :slight_smile: Have a great day!


#11

one more thing, this code:

def create_passwd():
    passwd = ""
    for i in range(40):
        passwd += random_char()
    return passwd

print(create_passwd())

i asked appylpye why he choice a string rather then a list + join:

def create_passwd():
   passwd = []
   for i in range(40):
       passwd.append(random_char()
   return "".join(passwd)

as appylpye explains it:

Using a list would be more efficient, because concatenating the string within a loop causes a whole new string to be created during each iteration, since strings are immutable, and lists are mutable

so using a list would be more more efficient, just a minor detail, it doesn't take away the fact that @appylpye wrote an amazing answer.


#12

Oh I appreciate that actually! I'm always looking for more efficiency because in real life, that's what people are looking for. In my program, I did use a string similar to what @appylpye did, but I I wasn't even thinking of efficiency.


#13

i think you could apply a similar approach here:

def shuffle(passwd):
    # Shuffle the order of the characters
    chars = list(passwd)
    passwd = ""
    while len(chars) > 0:
        passwd += chars.pop(random.randint(0, len(chars) - 1))
    return passwd

here you are also adding to a string, a list would be more efficient:

def shuffle(passwd):
    # Shuffle the order of the characters
    chars = list(passwd)
    passwd = []
    while len(chars) > 0:
        passwd.append(chars.pop(random.randint(0, len(chars) - 1)))
    return "".join(passwd)

yea, efficiency is good to learn. coming back to create_passwd function you could do:

def create_passwd():
   return "".join([random_char() for i in range(40)])

this is more optimization in the sense of reduce code base, then performance.


#14

Thanks for asking about the string versus list issue, @stetim94. That adds a lot to the value of the discussion. I wrote the program quickly without optimizing it. In actual practice, it's a very good idea to plan programs for efficiency.


#15

I've started to learn that a lot more as well. (That it's best to plan for efficiency.) It's super duper awesome that you wrote that program though, and I'm seriously applauding you for it. :slight_smile:


#16

Yeah for both @mtf and @appylpye and @stetim94. It is hard enough writing some codes with instructions and hints let alone something you just read about! Very impressive and I hope to be able to do similar! Especially in my college computer classes... And btw impressive code yourself @kyleaw I bet your AP computer science teacher is happy and impressed!


#17

Thank you! Java has me pulling my hair out. But @appylpye really outdid himself writing that code quickly and about something I took forever to understand. I love that everybody gave me example's too. Honestly all of these moderators and amazing!


#18

@kyleaw ,

Has your AP Computer Science class discussed Big O notation? If not, see Big O notation.


#19

It hasn't at all, actually. Thank you for that, I'm definitely going to have to look into that! I don't know if it will or not, it's possible that it's waiting to, but I'm going to go ahead and get the prior knowledge now. :slight_smile:


#20

the big O notation is super useful to know, i recommend to learn it no matter if the computer science class covers it