/*
* Copyright 2012 Anton Van Zyl. http://code.google.com/p/java-swiss-knife/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* under the License.
*/
package com.knife.security;
import java.util.Random;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import com.knife.security.exception.InvalidPasswordLength;
/**
* This is the password utilities that I use the most. <br/>
* <br/>
* Please visit <a
* href="http://code.google.com/p/java-swiss-knife/">Java-Swiss-Knife</a> and
* comment, rate, contribute or raise a issue/enhancement for my library. <br/>
*
* @author Anton Van Zyl
*
*/
public final class PasswordUtil {
// Bonus scheme for passwords
private static final int Excess = 3;
private static final int Upper = 4;
private static final int Numbers = 3;
private static final int Symbols = 5;
private static final String regex = "[!,@,#,$,%,^,&,*,?,_,~]+";
//@formatter:off
protected static char[] lowerCaseAlpa = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
};
protected static char[] upperCaseAlpha = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
};
protected static char[] numbers = {
'1', '2', '3', '4', '5', '6', '7', '8', '9','0', ' ',
};
protected static char[] symbols = {
'+', '-', '@','!','#','$','%','^','&','*','?','_','~'
};
//@formatter:on
/**
* This will not generate a readable word but a sequence of chars at the
* specified length and complexity. The character selection is listed below
* that will be used when generating a valid password:
*
* <pre>
* <code>
* protected static char[] lowerCaseAlpa = {
* 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
* 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
* };
* protected static char[] upperCaseAlpha = {
* 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N',
* 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
* };
* protected static char[] numbers = {
* '1', '2', '3', '4', '5', '6', '7', '8', '9','0', ' ',
* };
* protected static char[] symbols = {
* '+', '-', '@','!','#','$','%','^','&','*','?','_','~'
* };
* </code>
* </pre>
*
* @param complexity
* - The complexity that the password will be generated at, this
* will always be equal to or above the specified complexity
* @return The password that is generated
*/
public static String generateRandomPassword(PasswordComplexity complexity) {
StringBuilder password = new StringBuilder(generatePassword(complexity));
try {
boolean foundPassword = false;
while (foundPassword == false) {
PasswordComplexity comp = checkPasswordStrength(password.toString(), complexity.getPasswordLength());
if (comp.ordinal() >= complexity.ordinal()) {
foundPassword = true;
} else {
new StringBuilder(generatePassword(complexity));
}
}
} catch (InvalidPasswordLength e) {
e.printStackTrace();
}
return password.toString();
}
/**
* Generates the password
* @param complexity
* @return password
*/
private static String generatePassword(PasswordComplexity complexity) {
StringBuilder password = new StringBuilder();
Random random = new Random();
switch (complexity) {
case WEAK:
for (int i = 0; i < complexity.getPasswordLength(); i++) {
password.append(lowerCaseAlpa[random.nextInt(lowerCaseAlpa.length)]);
}
break;
case BELOW_AVERAGE:
for (int i = 0; i < (complexity.getPasswordLength() - 1); i++) {
password.append(lowerCaseAlpa[random.nextInt(lowerCaseAlpa.length)]);
}
password.append(numbers[random.nextInt(numbers.length)]);
break;
case AVERAGE:
for (int i = 0; i < (complexity.getPasswordLength() / 2); i++) {
password.append(lowerCaseAlpa[random.nextInt(lowerCaseAlpa.length)]);
password.append(upperCaseAlpha[random.nextInt(upperCaseAlpha.length)]);
}
while (password.length() < complexity.getPasswordLength()) {
password.append(lowerCaseAlpa[random.nextInt(lowerCaseAlpa.length)]);
}
break;
case ABOVE_AVERAGE:
for (int i = 0; i < ((complexity.getPasswordLength()) / 2) - 1; i++) {
password.append(lowerCaseAlpa[random.nextInt(lowerCaseAlpa.length)]);
password.append(upperCaseAlpha[random.nextInt(upperCaseAlpha.length)]);
}
password.append(numbers[random.nextInt(numbers.length)]);
while (password.length() < complexity.getPasswordLength()) {
password.append(lowerCaseAlpa[random.nextInt(lowerCaseAlpa.length)]);
}
break;
case STRONG:
for (int i = 0; i < (complexity.getPasswordLength() / 3); i++) {
password.append(lowerCaseAlpa[random.nextInt(lowerCaseAlpa.length)]);
password.append(upperCaseAlpha[random.nextInt(upperCaseAlpha.length)]);
password.append(numbers[random.nextInt(numbers.length)]);
}
while (password.length() < complexity.getPasswordLength()) {
password.append(upperCaseAlpha[random.nextInt(upperCaseAlpha.length)]);
}
break;
case SECURE:
for (int i = 0; i < (complexity.getPasswordLength() / 4); i++) {
password.append(lowerCaseAlpa[random.nextInt(lowerCaseAlpa.length)]);
password.append(upperCaseAlpha[random.nextInt(upperCaseAlpha.length)]);
password.append(numbers[random.nextInt(numbers.length)]);
password.append(symbols[random.nextInt(symbols.length)]);
}
while (password.length() < complexity.getPasswordLength()) {
password.append(symbols[random.nextInt(symbols.length)]);
}
break;
}
return password.toString();
}
/**
* This will calculate the password input strength and return a enumeration
* representing the outcome of the calculation.
*
*
* @param passwordInput
* - the password to test against
* @param minPasswordLength
* - the minimum password length allowed (This is used in the
* calculation and is better to define)
* @return <code>PasswordComplexity</code> enumeration defining the password
* complexity state.
* @throws InvalidPasswordLength
* - when the entered password is below the specified length
*/
public static PasswordComplexity checkPasswordStrength(String passwordInput, int minPasswordLength) throws InvalidPasswordLength {
if (passwordInput == null) {
throw new NullPointerException("checkPasswordStrength: passwordInput is NULL");
}
int baseScore;
if (passwordInput.length() >= minPasswordLength) {
baseScore = 50;
Password password = analyzePassword(passwordInput, minPasswordLength);
baseScore = calculateComplexity(password, baseScore);
} else {
throw new InvalidPasswordLength("The password is incorrect length [minPasswordLength=" + minPasswordLength + ";passwordInputLength=" + passwordInput.length() + ";]");
}
return PasswordComplexity.calculateComplexity(baseScore);
}
/**
* Analyse a input string and creates a score to be calculated with. This is
* build by extracting the different characters that exist in the string.
*
* @param passwordInput
* - The string that is the password
* @param minPasswordLength
* - The length the password needs to be.
* @return - Password object that contains the count
*/
private static Password analyzePassword(final String passwordInput, final int minPasswordLength) {
Password password = new Password();
for (char value : passwordInput.toCharArray()) {
String stringValue = String.valueOf(value);
if (StringUtils.isNumeric(stringValue)) {
password.numbers++;
} else if (StringUtils.isAllUpperCase(stringValue)) {
password.upper++;
} else if (Pattern.matches(regex, stringValue) || value == ' ') {
password.symbols++;
}
}
password.excess = passwordInput.length() - minPasswordLength;
if (password.upper > 0 && password.numbers > 0 && password.symbols > 0) {
password.bonusCombo = 25;
} else if ((password.upper > 0 && password.numbers > 0) || (password.upper > 0 && password.symbols > 0) || (password.numbers > 0 && password.symbols > 0)) {
password.bonusCombo = 15;
}
if (StringUtils.isAllLowerCase(passwordInput)) {
password.bonusFlatLower = -15;
}
if (StringUtils.isNumeric(passwordInput)) {
password.bonusFlatNumber = -35;
}
return password;
}
/**
* Calculates the complexity of the password
*
* @param password
* - The password a object that holds the score
* @param baseScore
* - the base on which the score proceeds
* @return the score that the password received
*/
private static int calculateComplexity(final Password password, final int baseScore) {
int result = baseScore + (password.excess * Excess) + (password.upper * Upper) + (password.numbers * Numbers) + (password.symbols * Symbols) + password.bonusCombo
+ password.bonusFlatLower + password.bonusFlatNumber;
return result;
}
}