I recently learned about Passy . It provides the necessary functionality needed in its PasswordGenerator class . It randomly generates passwords that meet the requirements similar to what is written below, using CharacterRules, not PasswordCharacterSets, as I did below. Instead of storing a list of unused indexes for inserting random characters, it simply shuffles the character buffer after inserting characters that match the requirements.
Below, left earlier, I recommend using Passay, if your licensing allows it, this code should work differently and provides details of why the generated passwords are cryptographically strong
I ended up writing this code twice. Once, to get a random character result, but it turned out that the distribution of characters depends on the size of the character set (oops!). I rewrote it, and now you just have to copy / paste the code and change Main.java to the character sets you want. Although this could have been done differently, I think this is a relatively simple approach to get the right result, and I encourage reuse, commentary, criticism, and thoughtful change.
PasswordGenerator code is managed as follows:
- Min / Max Length : Set using a random number
- PasswordCharacterSet : it is assumed that all PasswordCharacterSet passed to PasswordGenerator consist of unique character sets, otherwise random characters will skew towards duplicates.
- Minimum characters PasswordCharacterSet : The minimum characters used for this character set.
The main bits for the actual password generation:
- Randomness of chance : we use SecureRandom, which is supported by a cryptographically strong PRNG, and not by the Random class, which is not one.
- Random character for password : all pw char array indices are added to the remaining indices array. When we call addRandomCharacters, it randomly deletes the index, and we use the remote index to populate the array.
- Random characters : addRandomCharacters selects a random index from the character index, which we use and add to the pw array.
- The minimum number of characters of each type is set : first, we simply select the minimum number of characters. We select the minimum number 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 remaining characters random for all character sets. All characters are added to one array. The remaining slots are filled using the same strategy for previous character sets.
Password complexity description: Password complexity is usually spoken in entropy bits. Here are the number of possibilities for your keyspace:
There is at least one uppercase alphabetic character (out of 26), one lowercase alphabetic character (out of 26), one digit (out of 10) and one special character (out of 32), a way to calculate the number of possibilities. this is the number of possibilities for each character multiplied by the number of characters, since they are arbitrarily placed in a string. So, we know the possibilities for four characters:
Required Characters = 26*26*10*32=216,320
All other characters have 94 (26 + 26 + 10 + 32) possibilities each
Our calculation:
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
Remember that if you want to get the most reliable passwords, you should always choose the maximum possible number of characters, which in this case is 14.
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;