-2

I am trying to generate a random password that includes special characters. The generated password string will return to the main method. This is the output that I get when I use 79 as my seed: koLN59 This is the output I am supposed to get when using 79 as my seed: koLN59#c I am not sure why the last two characters are not showing up. This is what I have so far:

public static String generateRandomPassword(Scanner sc) {  
    Scanner scanner = new Scanner(System.in);
System.out.println("Enter seed:");
long seedy = scanner.nextLong();

Random rand = new Random(seedy);
    
    char lowerCase1 =(char)(rand.nextInt(26) + 97);
    char lowerCase2 =(char)(rand.nextInt(26) + 97);
    char upperCase1 =(char)(rand.nextInt(26) + 65);
    char upperCase2 =(char)(rand.nextInt(26) + 65);
    int num1 = rand.nextInt(10);
    int num2 = rand.nextInt(10);
    char rand1 = (char)(rand.nextInt(33) + 126);
    char rand2 =(char)(rand.nextInt(33) + 126);
    
    String tempPass =("" + lowerCase1 + lowerCase2 + upperCase1 + upperCase2 + num1 + num2 + rand1 + rand2);
    
    return tempPass;
LOA
  • 23
  • 3
  • Why do you believe that `#` (35) and `c` (99) will result from adding **126**_(!!)_ to a random number in range 0-32? – Andreas Apr 16 '21 at 21:31
  • 1
    Consider using commons lang3 RandomStringUtils. – Kristof Neirynck Apr 16 '21 at 21:32
  • 2
    Did you perhaps mean for `rand.nextInt(33) + 126` to be `rand.nextInt(126 - 33 + 1) + 33`, i.e. `rand.nextInt(94) + 33`, to randomly pick any *displayable* ASCII character? – Andreas Apr 16 '21 at 21:38
  • Changing rand.nextInt(33) + 126 to rand.nextInt (126 - 33 + 1) + 33 did work! – LOA Apr 16 '21 at 22:02
  • 3
    @LOA, however, a password generated using specific "format" with the fixed length of just 8 ASCII characters is too weak. – Nowhere Man Apr 16 '21 at 23:09

1 Answers1

0

tl;dr

Your last two characters are not missing, they are undefined.

Your faulty math resulted in code points 155 & 138 which are not assigned to any character in Unicode.

Details

For real work, as others commented:

  • Your generateded passwords will be too short.
  • Your generated passwords will be too predictable.
  • For such important tasks, you should rely on proven libraries that are already written, reviewed, and tested.

But for fun, I will take on your challenge.

Last two characters missing

First to address your specific question:

output that I get when I use 79 as my seed: koLN59 This is the output I am supposed to get when using 79 as my seed: koLN59#c I am not sure why the last two characters are not showing up.

As commented by Andreas, your math is wrong.

You are trying to get a couple of specific characters, # NUMBER SIGN at code point 35, and c LATIN SMALL LETTER C at code point 99.

To get there, you are starting with a positive random number from 0-32 inclusive (33 exclusive). Then you add to 126 (which in Unicode is TILDE ~). So there is no way mathematically to get code points 35 & 99 when you are adding any positive random integer to 126.

Furthermore, you should not be adding small numbers to 126. Code points 127 to 159 inclusive are not assigned in Unicode. See Wikipedia list of code points & characters.

Code points 155 & 138 not defined in Unicode.

➥ Your code is resulting in rand1 being code point 155, and rand2 being code point 138. Neither 155 nor 138 are defined in Unicode. Hence nothing appearing on your console.

See this version of your code:

package work.basil.demo.pw;

import java.util.Random;

public class Broken
{
    public static String generateRandomPassword ( )
    {
        int seed = 79;

        Random rand = new Random( seed );

        char lowerCase1 = ( char ) ( rand.nextInt( 26 ) + 97 );
        char lowerCase2 = ( char ) ( rand.nextInt( 26 ) + 97 );
        char upperCase1 = ( char ) ( rand.nextInt( 26 ) + 65 );
        char upperCase2 = ( char ) ( rand.nextInt( 26 ) + 65 );
        int num1 = rand.nextInt( 10 );
        int num2 = rand.nextInt( 10 );
        char rand1 = ( char ) ( rand.nextInt( 33 ) + 126 );
        char rand2 = ( char ) ( rand.nextInt( 33 ) + 126 );

        System.out.println( "rand1 int = " + ( int ) rand1 );
        System.out.println( "rand2 int = " + ( int ) rand2 );

        String tempPass = ( "" + lowerCase1 + lowerCase2 + upperCase1 + upperCase2 + num1 + num2 + rand1 + rand2 );
        System.out.println( "length of tempPass: " + tempPass.length() );

        return tempPass;
    }

    public static void main ( String[] args )
    {
        System.out.println(
                Broken.generateRandomPassword()
        );
    }
}

run live at IdeOne.com. Notice the length: 8, not 6. You are getting the last two characters but they are not defined. So no glyph appears.

rand1 int = 155
rand2 int = 138
length of tempPass: 8
koLN59

char type is obsolete

Avoid the char type in Java. This type is now legacy, unable to represent even half of the 143,859 characters defined in Unicode.

Instead, use the code point integer numbers assigned to characters in Unicode. These range from zero to just over a million.

In the code below, we just use the String type.

Separate UI from logic

Tip: Software is usually better designed when following the rule of Separation of concerns. Separate out different features and responsibilities into separate chunks of code.

Generally your user-interface code should be separated from your business logic. In your case, this means separating your console interactions with the user from the code that generates the passwords.

Notice in code below that we have one class with a method for the password generation. We have a separate class that uses the password-generator class using hard-coded values. You should add a third separate class that uses Scanner to interact with the user in order to invoke the password-generator.

Example code

Rather than play math tricks with code point integers, I would work with arrays or lists of your various permitted characters. Then use a random number as an index into that string to retrieve a particular character.

Here we use a cryptographically strong random number generator, SecureRandom, rather than Random.

This code uses the new records feature in Java 16. But you could rewrite that easily for earlier Java.

package work.basil.demo.pw;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class PasswordGenerator
{
    // Static fields
    static private int[] lowercase = "abcdefghijklmnopqrstuvwxyz".codePoints().toArray();
    static private int[] uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".codePoints().toArray();
    static private int[] digits = "1234567890".codePoints().toArray();
    static private int[] punctuation = "!@#$%".codePoints().toArray();

    // Member fields.
    java.security.SecureRandom random = new java.security.SecureRandom();


    public String generate ( final int minLength , final int maxLength , final int minLowercase , final int maxLowercase , final int minUppercase , final int maxUppercase , final int minDigits , final int maxDigits )
    {
        // Validate inputs.
        int maxTotal = ( maxLowercase + maxUppercase + maxDigits );
        if ( maxTotal >= maxLength )
        {
            throw new IllegalArgumentException( "Your maximums for lowercase, uppercase, and digits meets or exceeds maximum password length. That leaves no room for punctuation. Message # c525776c-24c1-42de-8638-75cd2ae92f53." );
        }

        // Prepare to build password.
        int passwordLength = this.randomInRange( minLength , maxLength );
        final List < String > characters = new ArrayList <>( passwordLength );

        // Build password
        record Input(int[] array , int count)
        {
        }
        final List < Input > inputs =
                new ArrayList <>(
                        List.of(
                                new Input( lowercase , this.randomInRange( minLowercase , maxLowercase ) ) ,
                                new Input( uppercase , this.randomInRange( minUppercase , maxUppercase ) ) ,
                                new Input( lowercase , this.randomInRange( minDigits , maxDigits ) )
                        ) );
        inputs.add( new Input( punctuation , passwordLength - inputs.stream().mapToInt( input -> input.count ).sum() ) );
        for ( Input input : inputs )
        {
            for ( int i = 0 ; i < input.count ; i++ )
            {
                final int index = this.random.nextInt( input.array.length );
                final int codePoint = input.array[ index ];
                characters.add( Character.toString( codePoint ) );
            }
        }
        Collections.shuffle( characters );

        // Report new password.
        String result = characters.stream().collect( Collectors.joining() );
        System.out.println( "result = " + result + " with length: " + result.length() );
        return result;
    }

    private int randomInRange ( int minInclusive , int maxInclusive )
    {
        return ( int ) ( ( this.random.nextDouble() * ( maxInclusive + 1 - minInclusive ) ) + minInclusive ); // The maximum is *exclusive*, so add one to  make it effectively inclusive.
    }

    public static void main ( String[] args )
    {
        System.out.println(
                new PasswordGenerator().generate( 8 , 10 , 2 , 3 , 2 , 3 , 1 , 2 )
        );
    }
}

Java 17 getting enhanced random generators

While Java 16 is current today, Java 17 later this year will get more features in the area of random number generators.

See: JEP 356: Enhanced Pseudo-Random Number Generators.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154