# ECOO 2012 Regional - Problem #2: Password Strength

## The problem:

Passwords are the most basic information protection devices. But many people compromise the security of their data by choosing passwords that are easy for a human or a bot to guess. The table below summarizes one set of criteria for determining the strength of a password, and shows how to apply these criteria to two different example passwords.

```    Example 1: "ECOO()123abcd9876"        Example 2: "1234512345"
```
Score Description Ex1 Ex2
Length Score 4 points for each character in the password. +68 +40
Basic Requirements To qualify, must be at least 8 chars long and also contain 3 of the four basic character types (upper case letters, lower case letters, digits, and symbols*). Score two points for the length plus another two for each of the four types of characters it contains. +10 --
Upper Case Add (length - n) * 2, where n is the number of upper case letters and n > 0. +26 --
Lower Case Add (length - n) * 2, where n is the number of lower case letters and n > 0. +26 --
Digits Add 4 * n, where n is the number of digits, but only if n < length. +28 --
Symbols Add 6 * n, where n is the number of symbol characters. +12 --
Middle Digits and Symbols Add 2 * n, where n is the number of digits and symbols not in the first or last position. +16 +16
Letters Only If the password contains only letters, subtract 1 point for each letter. -- --
Digits Only If the password contains only digits, subtract 1 point for each digit. -- -10
Consecutive Upper Case Subtract 2 * (n - 1) for each set of n consecutive upper case characters. -6 --
Consecutive Lower Case Subtract 2 * (n - 1) for each set of n consecutive lower case characters. -6 --
Consecutive Digits Subtract 2 * (n - 1) for each set of n consecutive digits. -10 -18
Sequential Letters Subtract 3 * (n - 2), where n is the length of the longest case-sensitive sequence of letters longer than 2 (e.g. "abcd" or "CBA" but not "aBcD" or "SJD" or "a"). -6 --
Sequential Digits Subtract 3 * (n - 2), where n is the length of the longest sequence of digits longer than 2 (e.g. "9876" but not "28" or "6"). -6 -9
TOTAL SCORE Add up all the above. Negative scores become 0. Scores over 100 become 100. 0-20 = Very Weak, 21-40 = Weak, 41-60 = Good, 61-80 = Strong, 81-100 = Very Strong 100
VERY
STRONG
19
VERY
WEAK
*A symbol is any character that is not a letter or digit.

DATA21.txt (DATA22.txt for the second try) contains ten passwords, one per line. Write a program that reads each password, computes its total score based on the above criteria, and categorizes the password as Very Weak, Weak, Good, Strong, or Very Strong.

 Sample Input ECOO()123abcd9876 1234512345 ecoo2012 Sample Output Very Strong (score = 100) Very Weak (score = 19) Good (score = 47)

## My solution (in Java):

```import java.io.File;
import java.util.ArrayList;
import java.util.Scanner;

static int score; // to be modified by methods

public static void main(String[] args) {

try {
Scanner file = new Scanner(new File("C:\\Users\\Mike\\Desktop\\problem_2_password_strength_DATA20.txt"));
ArrayList<String> list = new ArrayList<String>();

// read in the file to the list
while (file.hasNextLine()) {
}

for (String password : list) {
score = 0; // reset it each password

// call methods to alter the score based on the password grading

if (password.matches("[0-9]+")) // regex; only digits
score -= password.length(); // -1 for each digit
if (password.matches("[a-zA-Z]+")) // regex; only letters
score -= password.length(); // -1 for each letter

// check consecutive types for each (uppercase letters, lowercase letters and digits)

// negative score goes to 0, score > 100 goes to 100
if (score < 0) score = 0;
if (score > 100) score = 100;

// display output based on intervals
if (score >= 0 && score <= 20) System.out.println("Very Weak (score = " + score + ")");
if (score >= 21 && score <= 40) System.out.println("Weak (score = " + score + ")");
if (score >= 41 && score <= 60) System.out.println("Good (score = " + score + ")");
if (score >= 61 && score <= 80) System.out.println("Strong (score = " + score + ")");
if (score >= 81 && score <= 100) System.out.println("Very Strong (score = " + score + ")");
}
} catch (Exception e) {
e.printStackTrace();
}
}

public static void middles(String password) {
for (int i = 1; i < password.length() - 1; i++) { // for every character in password string
if (c.matches("[^a-zA-Z]")) { // not a letter, so a digit or symbol
score += 2;
}
}
}

/* parameter s is the password string
and regex is the type to check.
ex: digits would be "[0-9]" or "\\d" */
public static void consecutiveTypes(String s, String regex) {

// ArrayList that holds the length of consecutive set (as element) for each consecutive set (each set its own element)
ArrayList<Integer> type = new ArrayList<Integer>();

for (int i = 0; i < s.length()-1; i++) { // for every character in password

int count = 1; // length of consecutive set
String s1 = s.substring(i,i+1), s2 = s.substring(i+1,i+2); // adjacent characters

// while loop to keep iterating through a consecutive set
while (s1.matches(regex) && s2.matches(regex)) { // if adjacent are both the same type
count++; // length of consecutive set increases
i++; // move over one index in the password because we've counted it in the consecutive set

// can't check any more adjacent characters because we're at the end of the password, so break
if (i > s.length() - 2) {
break;
}
s1 = s.substring(i,i+1);
s2 = s.substring(i+1,i+2);
}
type.add(count); // add length of consecutive set to list once the while loop is done (consecutive set is over)
}

// update the score
for (int length : type) { // for every number length of consecutive set of every consecutive set
score -= 2 * (length - 1);
}
}

public static void sequentialTypes(String s) {

// ArrayLists that hold the length of sequential sets (as element) for each sequential set (each set its own element)
ArrayList<Integer> letters = new ArrayList<Integer>(), digits = new ArrayList<Integer>();

for (int i = 0; i < s.length()-1; i++) { // for every character in password

int count = 1; // length of sequential set
char c1 = s.charAt(i), c2 = s.charAt(i+1); // adjacent characters

// check if sequence should be increasing or decreasing (difference in ASCII values positive or negative)
int upOrDown;
if ((int)(c1) - (int)(c2) < 0) upOrDown = -1;
else upOrDown = 1;

// while loop to keep iterating through a consecutive set
// both characters are digits; and the difference between the two ASCII values is 1 (they're sequential) and following the sequential trend
while (String.valueOf(c1).matches("[0-9]") && String.valueOf(c2).matches("[0-9]") && (int)(c1) - (int)(c2) == upOrDown) {
count++; // length of sequential set increases
i++; // move over one index in the password because we've counted it in the sequential set

// can't check any more adjacent characters because we're at the end of the password, so break
if (i > s.length() - 2) {
break;
}

c1 = s.charAt(i);
c2 = s.charAt(i+1);
}
digits.add(count); // add length of sequential set to list once the while loop is done (sequential set is over)
}

// do the same thing as the above for loop (with digit type), but instead with lowercase letters and then again with uppercase letters
// but add the sequential lengths to the same list because we are checking sequential LETTERS, so the longest
// sequential set length needs to include both lowercase and uppercase for the letters
for (int i = 0; i < s.length()-1; i++) {

int count = 1;
char c1 = s.charAt(i), c2 = s.charAt(i+1);

int upOrDown;
if ((int)(c1) - (int)(c2) < 0) upOrDown = -1;
else upOrDown = 1;

while (String.valueOf(c1).matches("[a-z]") && String.valueOf(c2).matches("[a-z]") && (int)(c1) - (int)(c2) == upOrDown) {
count++;
i++;

if (i > s.length() - 2) {
break;
}

c1 = s.charAt(i);
c2 = s.charAt(i+1);
}
}
for (int i = 0; i < s.length()-1; i++) {

int count = 1;
char c1 = s.charAt(i), c2 = s.charAt(i+1);

int upOrDown;
if ((int)(c1) - (int)(c2) < 0) upOrDown = -1;
else upOrDown = 1;

while (String.valueOf(c1).matches("[A-Z]") && String.valueOf(c2).matches("[A-Z]") && (int)(c1) - (int)(c2) == upOrDown) {
count++;
i++;

if (i > s.length() - 2) {
break;
}

c1 = s.charAt(i);
c2 = s.charAt(i+1);
}
}

int max = 2; // initialize to 2 just incase there is no other max found, this way the default is less than what can be updated to the score
for (int i : digits) { // for every sequential digit set length
if (i > max) { // if this length is greater than the previous max
max = i; // this is the new max
}
}
if (max > 2) { // if length is longer than 2 (has to be a sequence of at least 3 in length)
score -= 3 * (max - 2); // update score
}

// do the same thing as above except now with every sequential LETTER set length
max = 2;
for (int i : letters) {
if (i > max) {
max = i;
}
}
if (max > 2) {
score -= 3 * (max - 2);
}
}

public static void counts(String password) {
// counts of each
int uppers = 0, lowers = 0, digits = 0, symbols = 0;

for (int i = 0; i < password.length(); i++) { // for every character in password

// count occurrences
if (c.matches("[a-z]")) { // lowercase
lowers++;
} else if (c.matches("[A-Z]")) { // uppercase
uppers++;
} else if (c.matches("[0-9]")) { // digits
digits++;
} else if (c.matches("[^a-zA-Z0-9]")) { // symbols
symbols++;
}
}

if (uppers > 0) {
score += (password.length() - uppers) * 2;
}
if (lowers > 0) {
score += (password.length() - lowers) * 2;
}
score += 4 * digits;
}
score += 6 * symbols;
}

/* 8 length
* 3 of: uppercase, lowercase, symbol, digit
* digits: 48-57
*/
public static void basicRequirements(String password) {

boolean longEnough = false;
longEnough = true;

int types = 0;

boolean digit = false, uppercase = false, lowercase = false, symbol = false;

for (int i = 0; i < password.length(); i++) { // for every character in password string

// current character in password string

if (s.matches("[0-9]")) // regex
//if (Character.isDigit(ch)) // Character class
//if (ch >= 48 && ch <= 57) // ASCII value
digit = true;
else if (s.matches("[A-Z]")) // regex
//else if (Character.isUpperCase(ch)) // Character class
//else if (ch >= 65 && ch <= 90) // ASCII value
uppercase = true; //
else if (s.matches("[a-z]")) // regex
//else if (Character.isLowerCase(ch)) // Character class
//else if (ch >= 97 && ch <= 122) // ASCII value
lowercase = true;
else if (s.matches("[^a-zA-Z0-9]")) // regex
//else if (!Character.isLetterOrDigit(ch)) // Character class
//else if (ch < 48 || (ch > 57 && ch < 65) || (ch < 97 && ch > 90) || ch > 122) // ASCII value
//else
symbol = true;
}

if (symbol) types++;
if (digit) types++;
if (uppercase) types++;
if (lowercase) types++;

boolean qualify = false;
if (types >= 3 && longEnough)
qualify = true;

if (qualify) {
score += 2 + types * 2;
}
}
}```

## My test cases (as .txt files):

Using their sample input file:

```ECOO()123abcd9876
1234512345
ecoo2012
ecoo
Ecoo
EcooEcoo123
(TheBest?)45(45)abCD
Ecoo123
39283
foo()
foobar()```

And the output to that is:

```Very Strong (score = 100)
Very Weak (score = 19)
Good (score = 47)
Very Weak (score = 6)
Very Weak (score = 16)
Very Strong (score = 81)
Very Strong (score = 100)
Good (score = 53)
Very Weak (score = 13)
Weak (score = 34)
Weak (score = 40)```

Using their first judging test input:

```12345678
a
(**)
1abcCBA2
abcdcbAedcba
education2012()
Education()
(An)(ApPlE)(a)(DaY)(kEePs)(ThE)(dOcToR)(aWaY)
aBc
abcdefg0987654321```

And the output to that is:

```Very Weak (score = 4)
Very Weak (score = 3)
Good (score = 44)
Good (score = 57)
Weak (score = 33)
Very Strong (score = 93)
Strong (score = 78)
Very Strong (score = 100)
Very Weak (score = 15)
Strong (score = 80)```

And that is exactly the expected output.

Using their second judging test input:

```RABBITRABBIT
!nopasaran!
god
LifeBeginsAt40
0
012
aBcDeFgHiJkLmNo
ponmlkj543
AAAAAAAAAAAAAAAAAA```

And the output to that is:

```Very Weak (score = 14)
Good (score = 41)
Very Weak (score = 5)
Very Strong (score = 92)
Very Strong (score = 100)
Very Weak (score = 3)
Very Weak (score = 4)
Strong (score = 75)
Weak (score = 28)
Very Weak (score = 20)```