package com.Ostermiller.util;
import java.security.SecureRandom;
import java.text.MessageFormat;
import java.util.ResourceBundle;
import java.util.Locale;
import java.util.Vector;
public class RandPass {
public static final String version = "1.1";
protected static ResourceBundle labels = ResourceBundle.getBundle("com.Ostermiller.util.RandPass", Locale.getDefault());
private static final int DEFAULT_PASSWORD_LENGTH = 8;
public static final char[] NUMBERS_AND_LETTERS_ALPHABET = {
'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','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','0','1','2','3',
'4','5','6','7','8','9',
};
public static final char[] SYMBOLS_ALPHABET = {
'!','\"','#','$','%','&','\'','(',
')','*','+',',','-','.','/',':',
';','<','?','@','[','\\',']','^',
'_','`','{','|','}','~',
};
public static final char[] PRINTABLE_ALPHABET = {
'!','\"','#','$','%','&','\'','(',
')','*','+',',','-','.','/','0',
'1','2','3','4','5','6','7','8',
'9',':',';','<','?','@','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',
'[','\\',']','^','_','`','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',
'{','|','}','~',
};
public static final char[] LOWERCASE_LETTERS_ALPHABET = {
'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',
};
public static final char[] LOWERCASE_LETTERS_AND_NUMBERS_ALPHABET = {
'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','0','1','2','3','4','5',
'6','7','8','9',
};
public static final char[] LETTERS_ALPHABET = {
'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','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',
};
public static final char[] UPPERCASE_LETTERS_ALPHABET = {
'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',
};
public static final char[] NONCONFUSING_ALPHABET = {
'A','B','C','D','E','F','G','H',
'J','K','M','N','P','Q','R','S',
'T','W','X','Y','Z','a','b','c',
'd','e','f','g','h','j','k','m',
'n','p','q','r','s','t','w','x',
'y','z','2','3','4','5','6','7',
'8','9',
};
protected SecureRandom rand;
protected int repetition = -1;
protected char[] alphabet;
protected char[] firstAlphabet;
protected char[] lastAlphabet;
public RandPass(){
this(new SecureRandom(), NONCONFUSING_ALPHABET);
}
public RandPass(SecureRandom rand){
this(rand, NONCONFUSING_ALPHABET);
}
public RandPass(char[] alphabet){
this(new SecureRandom(), alphabet);
}
public RandPass(SecureRandom rand, char[] alphabet){
this.rand = rand;
this.alphabet = alphabet;
}
private class Requirement {
private Requirement(char[] alphabet, int num){
this.alphabet = alphabet;
this.num = num;
}
private char[] alphabet;
private int num;
}
private enum RandPassCmdLnOption {
HELP(new CmdLnOption(labels.getString("help.option")).setDescription( labels.getString("help.message"))),
VERSION(new CmdLnOption(labels.getString("version.option")).setDescription(labels.getString("version.message"))),
ABOUT(new CmdLnOption(labels.getString("about.option")).setDescription(labels.getString("about.message"))),
ALPHABET(new CmdLnOption(labels.getString("alphabet.option"), 'a').setRequiredArgument().setDescription(labels.getString("a.message"))),
FIRST(new CmdLnOption(labels.getString("first.alphabet.option"), 'F').setRequiredArgument().setDescription(labels.getString("F.message"))),
LAST(new CmdLnOption(labels.getString("last.alphabet.option"), 'L').setRequiredArgument().setDescription(labels.getString("L.message"))),
NUMBER(new CmdLnOption(labels.getString("number.option"), 'n').setRequiredArgument().setDescription(labels.getString("n.message"))),
REPS(new CmdLnOption(labels.getString("maxrep.option"), 'r').setRequiredArgument().setDescription(labels.getString("r.message"))),
LENGTH(new CmdLnOption(labels.getString("length.option"), 'l').setRequiredArgument().setDescription(labels.getString("l.message"))),
REQUIRE(new CmdLnOption(labels.getString("require.option"), 'R').setRequiredArgument().setDescription(labels.getString("R.message"))),
VERIFY(new CmdLnOption(labels.getString("verify.option"), 'v').setRequiredArgument().setDescription(labels.getString("v.message")));
private CmdLnOption option;
private RandPassCmdLnOption(CmdLnOption option){
option.setUserObject(this);
this.option = option;
}
private CmdLnOption getCmdLineOption(){
return option;
}
}
public static void main(String[] args) throws Exception {
CmdLn commandLine = new CmdLn(
args
).setDescription(
labels.getString("randpass") + labels.getString("purpose.message")
);
for (RandPassCmdLnOption option: RandPassCmdLnOption.values()){
commandLine.addOption(option.getCmdLineOption());
}
int number = 1;
char[] alphabet = NONCONFUSING_ALPHABET;
char[] firstAlphabet = null;
char[] lastAlphabet = null;
Vector<String> reqs = new Vector<String>();
Vector<String> ver = new Vector<String>();
int maxreps = 0;
int length = 8;
for(CmdLnResult result: commandLine.getResults()){
switch((RandPassCmdLnOption)result.getOption().getUserObject()){
case HELP:{
commandLine.printHelp();
System.exit(0);
} break;
case VERSION:{
System.out.println(MessageFormat.format(labels.getString("version"), (Object[])new String[] {version}));
System.exit(0);
} break;
case ABOUT:{
System.out.println(
labels.getString("randpass") + " -- " + labels.getString("purpose.message") + "\n" +
MessageFormat.format(labels.getString("copyright"), (Object[])new String[] {"2001-2007", "Stephen Ostermiller (http://ostermiller.org/contact.pl?regarding=Java+Utilities)"}) + "\n\n" +
labels.getString("license")
);
System.exit(0);
} break;
case ALPHABET:{
String alph = result.getArgument();
if (alph.length() == 0){
alphabet = NONCONFUSING_ALPHABET;
} else {
alphabet = alph.toCharArray();
}
} break;
case FIRST:{
String alph = result.getArgument();
if (alph.length() == 0){
firstAlphabet = null;
} else {
firstAlphabet = alph.toCharArray();
}
} break;
case LAST:{
String alph = result.getArgument();
if (alph.length() == 0){
lastAlphabet = null;
} else {
lastAlphabet = alph.toCharArray();
}
} break;
case REQUIRE:{
String alph = result.getArgument();
if (alph.length() != 0){
reqs.add(alph);
}
} break;
case VERIFY:{
ver.add(result.getArgument());
} break;
case NUMBER:{
try {
number = Integer.parseInt(result.getArgument());
if (number <= 0) throw new NumberFormatException();
} catch (NumberFormatException nfe){
System.err.println(labels.getString("number.bad_argument"));
System.exit(0);
}
} break;
case REPS:{
try {
maxreps = Integer.parseInt(result.getArgument());
if (maxreps < 0) throw new NumberFormatException();
} catch (NumberFormatException nfe){
System.err.println(labels.getString("number.bad_argument"));
System.exit(0);
}
} break;
case LENGTH:{
try {
length = Integer.parseInt(result.getArgument());
if (length < 0) throw new NumberFormatException();
} catch (NumberFormatException nfe){
System.err.println(labels.getString("number.bad_argument"));
System.exit(0);
}
} break;
}
}
RandPass randPass = new RandPass();
randPass.setAlphabet(alphabet);
randPass.setFirstAlphabet(firstAlphabet);
randPass.setLastAlphabet(lastAlphabet);
randPass.setMaxRepetition(maxreps);
for (int i=0; i<reqs.size(); i++){
randPass.addRequirement((reqs.elementAt(i)).toCharArray(), 1);
}
for (int i=0; i<ver.size(); i++){
randPass.addVerifier((PasswordVerifier)((Class.forName((ver.elementAt(i)))).newInstance()));
}
for (int i=0; i<number; i++){
System.out.println(randPass.getPass(length));
}
}
private Vector<Requirement> requirements = null;
public void addRequirement(char[] alphabet, int num){
if (requirements == null) requirements = new Vector<Requirement>();
requirements.add(new Requirement(alphabet, num));
}
public void setAlphabet(char[] alphabet){
if (alphabet == null) throw new NullPointerException("Null alphabet");
if (alphabet.length == 0) throw new ArrayIndexOutOfBoundsException("No characters in alphabet");
this.alphabet = alphabet;
}
public void setRandomGenerator(SecureRandom rand){
this.rand = rand;
}
public void setFirstAlphabet(char[] alphabet){
if (alphabet == null || alphabet.length == 0){
this.firstAlphabet = null;
} else {
this.firstAlphabet = alphabet;
}
}
public void setLastAlphabet(char[] alphabet){
if (alphabet == null || alphabet.length == 0){
this.lastAlphabet = null;
} else {
this.lastAlphabet = alphabet;
}
}
public void setMaxRepetition(int rep){
this.repetition = rep - 1;
}
public char[] getPassChars(char[] pass){
boolean verified = false;
while (!verified){
int length = pass.length;
for (int i=0; i<length; i++){
char[] useAlph = alphabet;
if (i == 0 && firstAlphabet != null){
useAlph = firstAlphabet;
} else if (i == length - 1 && lastAlphabet != null){
useAlph = lastAlphabet;
}
int size = avoidRepetition(useAlph, pass, i, repetition, useAlph.length);
pass[i] = useAlph[rand.nextInt(size)];
}
if (requirements != null) applyRequirements(pass);
verified = true;
for (int i=0; verified && verifiers != null && i<verifiers.size(); i++){
verified = verifiers.elementAt(i).verify(pass);
}
}
return(pass);
}
private Vector<PasswordVerifier> verifiers = null;
public void addVerifier(PasswordVerifier verifier){
if (verifiers == null) verifiers = new Vector<PasswordVerifier>();
verifiers.add(verifier);
}
private boolean[] touched = null;
private int[] available = null;
private void applyRequirements(char[] pass){
int size = requirements.size();
if (size > 0){
int length = pass.length;
if (touched == null || touched.length < length) touched = new boolean[length];
if (available == null || available.length < length) available = new int[length];
for (int i=0; i<length; i++){
touched[i] = false;
}
for (int reqNum=0; reqNum<size; reqNum++){
Requirement req = requirements.elementAt(reqNum);
int reqUsedInd = req.alphabet.length;
int fufilledInd = 0;
int availableInd = 0;
for (int i=0; i<length; i++){
if (arrayContains(req.alphabet, pass[i]) && fufilledInd < req.num){
fufilledInd++;
touched[i] = true;
if (repetition >= 0){
reqUsedInd -= moveto(req.alphabet, reqUsedInd, pass[i]);
if(reqUsedInd < 0) reqUsedInd = req.alphabet.length;
}
} else if (!touched[i]){
available[availableInd] = i;
availableInd++;
}
}
int toDo = req.num - fufilledInd;
for (int i=0; i<toDo && availableInd>0; i++){
int slot = rand.nextInt(availableInd);
char passChar = req.alphabet[rand.nextInt(reqUsedInd)];
pass[available[slot]] = passChar;
touched[available[slot]] = true;
availableInd--;
available[slot] = available[availableInd];
if (repetition >= 0){
reqUsedInd -= moveto(req.alphabet, reqUsedInd, passChar);
if(reqUsedInd < 0) reqUsedInd = req.alphabet.length;
}
}
}
}
}
private static boolean arrayContains(char[] alph, char c){
for (int i=0; i<alph.length; i++){
if (alph[i] == c) return true;
}
return false;
}
private static int avoidRepetition(char[] alph, char[] pass, int passSize, int repetition, int alphSize){
if (repetition > -1){
int repPos = 0;
while ((repPos = findRep(pass, repPos, passSize, repetition)) != -1){
alphSize -= moveto(alph, alphSize, pass[repPos+repetition]);
repPos++;
}
if (alphSize == 0) alphSize = alph.length;
}
return alphSize;
}
private static int findRep(char[] pass, int start, int end, int length){
for (int i=start; i<end-length; i++){
boolean onTrack = true;
for (int j=0; onTrack && j<length; j++){
if (pass[i+j] != pass[end-length+j]) onTrack = false;
}
if(onTrack) return i;
}
return -1;
}
private static int moveto(char[] alph, int numGood, char c){
int count = 0;
for (int i=0; i<numGood; i++){
if (alph[i] == c){
numGood--;
char temp = alph[numGood];
alph[numGood] = alph[i];
alph[i] = temp;
count++;
}
}
return count;
}
public char[] getPassChars(int length){
return(getPassChars(new char[length]));
}
public char[] getPassChars(){
return(getPassChars(DEFAULT_PASSWORD_LENGTH));
}
public String getPass(int length){
return(new String(getPassChars(new char[length])));
}
public String getPass(){
return(getPass(DEFAULT_PASSWORD_LENGTH));
}
}