Generate a Secure Random Password in Java with Minimum Special Character Requirements

I suggest using apache commons RandomStringUtils. Use something what is already done.

String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^&*()-_=+[{]}\\|;:\'\",<.>/?";
String pwd = RandomStringUtils.random( 15, characters );
System.out.println( pwd );

If you are using maven

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>

otherwise download jar

UPDATE Version with secure random. So matter of required characters left and can be solved as in comment, generate required parts separately and normal ones. Then join them randomly.

char[] possibleCharacters = (new String("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^&*()-_=+[{]}\\|;:\'\",<.>/?")).toCharArray();
String randomStr = RandomStringUtils.random( randomStrLength, 0, possibleCharacters.length-1, false, false, possibleCharacters, new SecureRandom() );
System.out.println( randomStr );

I recently learned about Passay. It provides the required functionality needed in its PasswordGenerator class. It randomly generates passwords meeting the requirements similar to what is written below using CharacterRules rather than PasswordCharacterSets as I have done below. Instead of holding a list of unused indexes for random character insertion, it simply shuffles the character buffer after inserting characters that meet the requirements.

Below is left over from before, I recommend using Passay if your licensing allows it, this code should work otherwise and provides details of why the generated passwords are crytographically strong

I ended up writing this code twice. Once to get a random character result, but it turned out the distribution of characters depended on the size of the character set(whoops!). I rewrote it and now you should just copy/paste the code and change the Main.java to the character sets you want. Although it could have been done differently, I think this is a relatively straightforward approach to get the correct result and I encourage reuse, comments, criticisms, and well-thought edits.

The controls of the PasswordGenerator code is as follows:

  • Min/Max Length: Set using a random number
  • PasswordCharacterSet: It is assumed that all PasswordCharacterSets passed into PasswordGenerator consist of unique character sets, if not, the random characters will have a skew towards the duplicates.
  • PasswordCharacterSet Min Characters: The min characters to use for this character set.

The main bits for the actual password generation:

  • Randomness of Random: We're using SecureRandom which is backed by a cryptographically strong PRNG, rather than the Random class which is not.
  • Random character order for the password: All the indexes of the pw char array are added to the remainingIndexes array. As we call addRandomCharacters, it removes an index randomly and we use the removed index to populate the array.
  • Random characters: In addRandomCharacters, a random index from the character index we're using is chosen and added to the pw array.
  • Guaranteeing minimum characters of each type are set: We simply carve out the minimum character amount first. We choose the minimum amount of random values from each character set and then move on.
  • Random distribution for the remaining characters: After the minimum values have been set, we want to make the rest of the characters random across all character sets. All the characters are added to a single array. The remaining slots are filled using the same strategy for the previous character sets.

Description of password complexity: Password complexity is usually talked about in bits of entropy. Here are the number of possibilities for your keyspace:

There is at least one uppercase alpha character (out of 26), one lowercase alpha character(out of 26), one digit (out of 10), and one special character (out of 32), the way you calculate the number of possibilities is the number of possibilities for each character multiplied by the number of characters since they are randomly placed in the string. So we know the possibilities for four of the characters are:

Required Characters = 26*26*10*32=216,320

All remaining characters have 94 (26+26+10+32) possibilities each

Our calculation is:

Characters  Possibilities                                       Bits of Entropy
10 chars    216,320*94^6  = 149,232,631,038,033,920             ~2^57
11 chars    216,320*94^7  = 14,027,867,317,575,188,480          ~2^63
12 chars    216,320*94^8  = 1,318,619,527,852,067,717,120       ~2^70
13 chars    216,320*94^9  = 123,950,235,618,094,365,409,280     ~2^76
14 chars    216,320*94^10 = 11,651,322,148,100,870,348,472,320  ~2^83

With this is mind, if you want the most secure passwords, you should always choose the highest amount of characters possible which is 14 in this case.

Main.java

package org.redtown.pw;

import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;

import org.redtown.pw.PasswordGenerator.PasswordCharacterSet;

public class Main {

    public static void main(String[] args) {
        Set<PasswordCharacterSet> values = new HashSet<PasswordCharacterSet>(EnumSet.allOf(SummerCharacterSets.class));
        PasswordGenerator pwGenerator = new PasswordGenerator(values, 10, 14);
        for(int i=0; i < 10; ++i) {
            System.out.println(pwGenerator.generatePassword());
        }
    }

    private static final char[] ALPHA_UPPER_CHARACTERS = { 'A', 'B', 'C', 'D',
            'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
            'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
    private static final char[] ALPHA_LOWER_CHARACTERS = { 'a', 'b', 'c', 'd',
            'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
            'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
    private static final char[] NUMERIC_CHARACTERS = { '0', '1', '2', '3', '4',
            '5', '6', '7', '8', '9' };
    private static final char[] SPECIAL_CHARACTERS = { '~', '`', '!', '@', '#',
            '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '[', '{',
            ']', '}', '\\', '|', ';', ':', '\'', '"', ',', '<', '.', '>', '/',
            '?' };

    private enum SummerCharacterSets implements PasswordCharacterSet {
        ALPHA_UPPER(ALPHA_UPPER_CHARACTERS, 1),
        ALPHA_LOWER(ALPHA_LOWER_CHARACTERS, 1),
        NUMERIC(NUMERIC_CHARACTERS, 1),
        SPECIAL(SPECIAL_CHARACTERS, 1);

        private final char[] chars;
        private final int minUsage;

        private SummerCharacterSets(char[] chars, int minUsage) {
            this.chars = chars;
            this.minUsage = minUsage;
        }

        @Override
        public char[] getCharacters() {
            return chars;
        }

        @Override
        public int getMinCharacters() {
            return minUsage;
        }
    }
}

PasswordGenerator.java

package org.redtown.pw;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class PasswordGenerator {

    private final List<PasswordCharacterSet> pwSets;
    private final char[] allCharacters;
    private final int minLength;
    private final int maxLength;
    private final int presetCharacterCount;

    public PasswordGenerator(Collection<PasswordCharacterSet> origPwSets, int minLength, int maxLength) {
        this.minLength = minLength;
        this.maxLength = maxLength;

        // Make a copy of the character arrays and min-values so they cannot be changed after initialization
        int pwCharacters = 0;
        int preallocatedCharacters = 0;
        List<PasswordCharacterSet> pwSets = new ArrayList<PasswordCharacterSet>(origPwSets.size());
        for(PasswordCharacterSet origpwSet : origPwSets) {
            PasswordCharacterSet newPwSet = new PwSet(origpwSet);
            pwSets.add(newPwSet);
            pwCharacters += newPwSet.getCharacters().length;
            preallocatedCharacters += newPwSet.getMinCharacters();
        }
        this.presetCharacterCount = preallocatedCharacters;
        this.pwSets = Collections.unmodifiableList(pwSets);

        if (minLength < presetCharacterCount) {
            throw new IllegalArgumentException("Combined minimum lengths "
                    + presetCharacterCount
                    + " are greater than the minLength of " + minLength);
        }

        // Copy all characters into single array so we can evenly access all members when accessing this array
        char[] allChars = new char[pwCharacters];
        int currentIndex = 0;
        for(PasswordCharacterSet pwSet : pwSets) {
            char[] chars = pwSet.getCharacters();
            System.arraycopy(chars, 0, allChars, currentIndex, chars.length);
            currentIndex += chars.length;
        }
        this.allCharacters = allChars;
    }


    public char[] generatePassword() {
        SecureRandom rand = new SecureRandom();

        // Set pw length to minLength <= pwLength <= maxLength
        int pwLength = minLength + rand.nextInt(maxLength - minLength + 1);
        int randomCharacterCount = pwLength - presetCharacterCount;


        // Place each index in an array then remove them randomly to assign positions in the pw array
        List<Integer> remainingIndexes = new ArrayList<Integer>(pwLength);
        for(int i=0; i < pwLength; ++i) {
            remainingIndexes.add(i);
        }

        // Fill pw array
        char[] pw = new char[pwLength];
        for(PasswordCharacterSet pwSet : pwSets) {
            addRandomCharacters(pw, pwSet.getCharacters(), pwSet.getMinCharacters(), remainingIndexes, rand);
        }
        addRandomCharacters(pw, allCharacters, randomCharacterCount, remainingIndexes, rand);
        return pw;
    }

    private static void addRandomCharacters(char[] pw, char[] characterSet,
            int numCharacters, List<Integer> remainingIndexes, Random rand) {
        for(int i=0; i < numCharacters; ++i) {
            // Get and remove random index from the remaining indexes
            int pwIndex = remainingIndexes.remove(rand.nextInt(remainingIndexes.size()));

            // Set random character from character index to pwIndex
            int randCharIndex = rand.nextInt(characterSet.length);
            pw[pwIndex] = characterSet[randCharIndex];
        }
    }

    public static interface PasswordCharacterSet {
        char[] getCharacters();
        int getMinCharacters();
    }

    /**
     * Defensive copy of a passed-in PasswordCharacterSet
     */
    private static final class PwSet implements PasswordCharacterSet {
        private final char[] chars;
        private final int minChars;

        public PwSet(PasswordCharacterSet pwSet) {
            this.minChars = pwSet.getMinCharacters();
            char[] pwSetChars = pwSet.getCharacters();
            // Defensive copy
            this.chars = Arrays.copyOf(pwSetChars, pwSetChars.length);
        }

        @Override
        public char[] getCharacters() {
            return chars;
        }

        @Override
        public int getMinCharacters() {
            return minChars;
        }
    }
}

Here is a utility that uses just vanilla Java and implements the requirements. It basically gets one of each of the required character sets. Then populates the rest with random chars from the whole set. Then shuffles it all up.

public class PasswordUtils {

    static char[] SYMBOLS = (new String("^$*.[]{}()?-\"!@#%&/\\,><':;|_~`")).toCharArray();
    static char[] LOWERCASE = (new String("abcdefghijklmnopqrstuvwxyz")).toCharArray();
    static char[] UPPERCASE = (new String("ABCDEFGHIJKLMNOPQRSTUVWXYZ")).toCharArray();
    static char[] NUMBERS = (new String("0123456789")).toCharArray();
    static char[] ALL_CHARS = (new String("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789^$*.[]{}()?-\"!@#%&/\\,><':;|_~`")).toCharArray();
    static Random rand = new SecureRandom();

    public static String getPassword(int length) {
        assert length >= 4;
        char[] password = new char[length];

        //get the requirements out of the way
        password[0] = LOWERCASE[rand.nextInt(LOWERCASE.length)];
        password[1] = UPPERCASE[rand.nextInt(UPPERCASE.length)];
        password[2] = NUMBERS[rand.nextInt(NUMBERS.length)];
        password[3] = SYMBOLS[rand.nextInt(SYMBOLS.length)];

        //populate rest of the password with random chars
        for (int i = 4; i < length; i++) {
            password[i] = ALL_CHARS[rand.nextInt(ALL_CHARS.length)];
        }

        //shuffle it up
        for (int i = 0; i < password.length; i++) {
            int randomPosition = rand.nextInt(password.length);
            char temp = password[i];
            password[i] = password[randomPosition];
            password[randomPosition] = temp;
        }

        return new String(password);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(getPassword(8));
        }
    }
}