Skip to content

Commit

Permalink
Fixed issue with exclusion dictionary not matching partial passwords.
Browse files Browse the repository at this point in the history
Fixed another issue related to how dictionaries can be built.  We only support lower case words in the dictionary and do case insensitive matching based on that.  There was nothing obvious stopping a user from using upper case without even knowing though.  I added a builder class to fix that issue and updated the readme to reflect that.

I added test cases to test exclusion dictionaries, and reformatted any classes which were out of what the coding standard calls for.
  • Loading branch information
Tostino committed Feb 17, 2017
1 parent 242a627 commit 0bcab0c
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 58 deletions.
34 changes: 15 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Password #2, while not allowed by our policy, is only susceptible to a brute for
<dependency>
<groupId>me.gosimple</groupId>
<artifactId>nbvcxz</artifactId>
<version>1.3.2</version>
<version>1.3.3</version>
</dependency>
```

Expand All @@ -90,7 +90,7 @@ Password #2, while not allowed by our policy, is only susceptible to a brute for

### Standalone
To use as a stand-alone program, just compile, and run it by calling:
`java -jar nbvcxz-1.3.2.jar`
`java -jar nbvcxz-1.3.3.jar`
![alt text](http://i.imgur.com/9c070FX.png)

### Library
Expand All @@ -99,27 +99,23 @@ Below is a full example of the pieces you'd need to implement within your own ap
##### Configure and create object

###### All defaults
```java
```
// With all defaults...
Nbvcxz nbvcxz = new Nbvcxz();
```

###### Custom configuration
Here we're creating a custom configuration with a custom exclusion dictionary and minimum entropy
```java
```
// Create a map of excluded words on a per-user basis using a hypothetical "User" object that contains this info
int i = 0;
HashMap<String, Integer> excludeMap = new HashMap();
excludeMap.put(user.getFirstName(), i++);
excludeMap.put(user.getLastName(), i++);
excludeMap.put(user.getEmail(), i++);
// And more...

// Create a dictionary list containing all the default dictionaries
List<Dictionary> dictionaryList = ConfigurationBuilder.getDefaultDictionaries();

// Add our new exclusion dictionary to the list
dictionaryList.add(new Dictionary("exclude", excludeMap, true));
dictionaryList.add(new DictionaryBuilder()
.setDictionaryName("exclude")
.setExclusion(true)
.addWord(user.getFirstName(), 0)
.addWord(user.getLastName(), 0)
.addWord(user.getEmail(), 0)
.createDictionary());
// Create our configuration object and set our custom minimum
// entropy, and custom dictionary list
Expand All @@ -135,7 +131,7 @@ Nbvcxz nbvcxz = new Nbvcxz(configuration);
##### Estimate password strength

###### Simple
```java
```

This comment has been minimized.

Copy link
@jdhoek

jdhoek Feb 20, 2017

Contributor

Just curious: why are you removing these language markers?

This comment has been minimized.

Copy link
@Tostino

Tostino Feb 20, 2017

Author Collaborator

Was causing false errors for me in the intellij markdown editor, so that was the simplest solution to shut it up for me.

This comment has been minimized.

Copy link
@jdhoek

jdhoek Feb 20, 2017

Contributor

Ah understood. I've seen that problem in IntelliJ as well — I use IntelliJ for everything Java, but for any MarkDown files in a project I just use a text editor because of IntelliJ's lacklustre MarkDown support. :)

This does disable code highlighting in the README when viewed on GitHub though.

// Estimate password
Result result = nbvcxz.estimate(password);
Expand All @@ -145,7 +141,7 @@ return result.isMinimumEntropyMet();
###### Feedback
This part will need to be integrated into your specific front end, and really depends on your needs.
Here are some of the possibilities:
```java
```
// Get formatted values for time to crack based on the values we
// input in our configuration (we used default values in this example)
Expand Down Expand Up @@ -201,7 +197,7 @@ else
We have a passphrase/password generator as part of `nbvcxz` which very easy to use.

###### Passphrase
```java
```
// Generate a passphrase from the standard (eff_large) dictionary with 5 words with a "-" between the words
String pass1 = Generator.generatePassphrase("-", 5);
Expand All @@ -210,7 +206,7 @@ String pass2 = Generator.generatePassphrase(new Dictionary(...), "-", 5);
```

###### Password
```java
```
// Generate a random password with alphanumeric characters that is 15 characters long
String pass = Generator.generateRandomPassword(Generator.CharacterTypes.ALPHANUMERIC, 15);
```
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<groupId>me.gosimple</groupId>
<artifactId>nbvcxz</artifactId>
<packaging>jar</packaging>
<version>1.3.2</version>
<version>1.3.3</version>

<name>nbvcxz</name>
<description>Nbvcxz takes heavy inspiration from the zxcvbn library built by Dropbox, and in a lot of ways is
Expand Down
16 changes: 0 additions & 16 deletions src/main/java/me/gosimple/nbvcxz/matching/DictionaryMatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -227,22 +227,6 @@ public List<Match> match(final Configuration configuration, final String passwor
// Iterate through all our dictionaries
for (Dictionary dictionary : configuration.getDictionaries())
{
// Only match exclude dictionaries on full passwords
if (dictionary.isExclusion() && (start != 0 || end != password.length()))
{
continue;
}

// Match exact
{
Integer exact_rank = dictionary.getDictonary().get(split_password);
if (exact_rank != null)
{
matches.add(new DictionaryMatch(split_password, configuration, start, end - 1, split_password, exact_rank, new ArrayList<>(), dictionary.isExclusion(), false, dictionary.getDictionaryName(), 0));
continue;
}
}

// Match on lower
String lower_part = split_password.toLowerCase();
{
Expand Down
9 changes: 6 additions & 3 deletions src/main/java/me/gosimple/nbvcxz/matching/RepeatMatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import me.gosimple.nbvcxz.matching.match.RepeatMatch;
import me.gosimple.nbvcxz.resources.Configuration;

import java.util.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -51,11 +54,11 @@ public List<Match> match(final Configuration configuration, final String passwor
int endIndex = match.end(0) - 1;

Set<Character> character_set = new HashSet<>();
for(char character : repeatCharacters.toCharArray())
for (char character : repeatCharacters.toCharArray())
{
character_set.add(character);
}
if(character_set.size() <= 4)
if (character_set.size() <= 4)
{
matches.add(new RepeatMatch(baseToken, configuration, repeatCharacters, startIndex, endIndex));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ protected void setEntropy(double entropy)
@Override
final public double calculateEntropy()
{
return Math.max(0, entropy);
return Math.max(0, entropy);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package me.gosimple.nbvcxz.matching.match;

import me.gosimple.nbvcxz.resources.Configuration;
import me.gosimple.nbvcxz.resources.BruteForceUtil;
import me.gosimple.nbvcxz.resources.Configuration;

/**
* @author Adam Brusselback
Expand All @@ -13,7 +13,7 @@ public final class BruteForceMatch extends BaseMatch
*
* @param match the {@code String} we are creating the {@code BruteForceMatch} from.
* @param configuration the {@link Configuration} object.
* @param index the index in the password for this match.
* @param index the index in the password for this match.
*/
public BruteForceMatch(char match, Configuration configuration, int index)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package me.gosimple.nbvcxz.matching.match;

import me.gosimple.nbvcxz.resources.Configuration;
import me.gosimple.nbvcxz.resources.BruteForceUtil;
import me.gosimple.nbvcxz.resources.Configuration;

import java.util.List;
import java.util.ResourceBundle;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package me.gosimple.nbvcxz.matching.match;

import me.gosimple.nbvcxz.resources.Configuration;
import me.gosimple.nbvcxz.resources.BruteForceUtil;
import me.gosimple.nbvcxz.resources.Configuration;

import java.util.ResourceBundle;

Expand Down Expand Up @@ -36,7 +36,7 @@ public RepeatMatch(String match, Configuration configuration, String repeatingCh
private double getEntropy()
{
int cardinality = BruteForceUtil.getBrutForceCardinality(getRepeatingCharacters());
if(getRepeat() != getRepeatingCharacters().length())
if (getRepeat() != getRepeatingCharacters().length())
{
return Math.max(0, log2(cardinality * getRepeat() * getRepeatingCharacters().length()));
}
Expand Down
22 changes: 12 additions & 10 deletions src/main/java/me/gosimple/nbvcxz/resources/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ public class Configuration
private final long combinationAlgorithmTimeout;

/**
* @param passwordMatchers The list of {@link PasswordMatcher}s which will be used for matching
* @param guessTypes Map of types of guesses, and associated guesses/sec
* @param dictionaries List of {@link Dictionary} to use for the {@link DictionaryMatcher}
* @param adjacencyGraphs List of adjacency graphs to be used by the {@link SpacialMatcher}
* @param leetTable Leet table for use with {@link DictionaryMatcher}
* @param yearPattern Regex {@link Pattern} for use with {@link YearMatcher}
* @param minimumEntropy Minimum entropy value passwords should meet
* @param locale Locale for localized text and feedback
* @param passwordMatchers The list of {@link PasswordMatcher}s which will be used for matching
* @param guessTypes Map of types of guesses, and associated guesses/sec
* @param dictionaries List of {@link Dictionary} to use for the {@link DictionaryMatcher}
* @param adjacencyGraphs List of adjacency graphs to be used by the {@link SpacialMatcher}
* @param leetTable Leet table for use with {@link DictionaryMatcher}
* @param yearPattern Regex {@link Pattern} for use with {@link YearMatcher}
* @param minimumEntropy Minimum entropy value passwords should meet
* @param locale Locale for localized text and feedback
* @param combinationAlgorithmTimeout Timeout for the findBestMatches algorithm.
*/
public Configuration(List<PasswordMatcher> passwordMatchers, Map<String, Long> guessTypes, List<Dictionary> dictionaries, List<AdjacencyGraph> adjacencyGraphs, Map<Character, Character> leetTable, Pattern yearPattern, Double minimumEntropy, Locale locale, boolean distanceCalc, long combinationAlgorithmTimeout)
Expand Down Expand Up @@ -131,10 +131,12 @@ public boolean isDistanceCalc()
}

/**
*
* @return Return the timeout for the findBestMatches algorithm
*/
public long getCombinationAlgorithmTimeout() {return combinationAlgorithmTimeout; }
public long getCombinationAlgorithmTimeout()
{
return combinationAlgorithmTimeout;
}

/**
* @return Return the resource bundle which contains the text for everything but feedback
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/me/gosimple/nbvcxz/resources/Dictionary.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
/**
* Object used for dictionary matching. This allows users to implement custom dictionaries for different languages
* or specialized vocabulary.
* <p>
* Dictionaries need to use all lower case keys for the words they contain for the algorithm to work correctly.
*
* @author Adam Brusselback.
*/
Expand All @@ -21,7 +23,7 @@ public class Dictionary
* Object used for dictionary matching.
*
* @param dictionary_name unique name of dictionary.
* @param dictonary {@code Map} with the word and it's rank.
* @param dictonary {@code Map} with the word and it's rank. The key must be lowercase for the matching to work properly.
* @param exclusion {@code true} when desiring to disallow any password contained in this dictionary; {@code false} otherwise.
*/
public Dictionary(final String dictionary_name, final Map<String, Integer> dictonary, final boolean exclusion)
Expand All @@ -31,7 +33,7 @@ public Dictionary(final String dictionary_name, final Map<String, Integer> dicto
this.exclusion = exclusion;

// This is to optimize the distance calculation stuff
this.sorted_dictionary = new ArrayList<>(dictonary.keySet());
this.sorted_dictionary = new ArrayList<>(this.dictonary.keySet());
this.sorted_dictionary.sort(Comparator.comparing(String::length).thenComparing(String::compareTo));
this.sorted_dictionary_length_lookup = new HashMap<>();
for (int i = 0; i < sorted_dictionary.size(); i++)
Expand Down
64 changes: 64 additions & 0 deletions src/main/java/me/gosimple/nbvcxz/resources/DictionaryBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package me.gosimple.nbvcxz.resources;

import java.util.HashMap;
import java.util.Map;

/**
* Dictionary builder class to help properly build dictionaries.
*/
public class DictionaryBuilder
{
private String dictionary_name;
private Map<String, Integer> dictonary = new HashMap<>();
private boolean exclusion;

/**
* Set the dictionary name
*
* @param dictionary_name unique name of dictionary.
* @return the builder
*/
public DictionaryBuilder setDictionaryName(final String dictionary_name)
{
this.dictionary_name = dictionary_name;
return this;
}

/**
* Set if exclusion dictionary or not.
*
* @param exclusion {@code true} when desiring to disallow any password contained in this dictionary; {@code false} otherwise.
* @return the builder
*/
public DictionaryBuilder setExclusion(final boolean exclusion)
{
this.exclusion = exclusion;
return this;
}

/**
* Add word to dictionary.
*
* @param word key to add to the dictionary, will be lowercased.
* @param rank the rank of the word in the dictionary.
* Should increment from most common to least common if ranked.
* If unranked, an example would be if there were 500 values in the dictionary, every word should have a rank of 250.
* If exclusion dictionary, rank is unimportant (set to 0).
* @return the builder
*/
public DictionaryBuilder addWord(final String word, final int rank)
{
this.dictonary.put(word.toLowerCase(), rank);
return this;
}

/**
* Creates the dictionary.
*
* @return the dictionary
*/
public Dictionary createDictionary()
{
return new Dictionary(dictionary_name, dictonary, exclusion);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public class DictionaryUtil
/**
* Read a resource file with a list of entries (sorted by frequency) and use
* it to create a ranked dictionary.
* <p>
* The dictionary must contain only lower case values for the matching to work properly.
*
* @param fileName the name of the file
* @return the ranked dictionary (a {@code HashMap} which associated a
Expand Down Expand Up @@ -87,6 +89,8 @@ public static Map<String, Integer> loadUnrankedDictionary(final String fileName)
/**
* Read a resource file with a list of entries (sorted by frequency) and use
* it to create a ranked dictionary.
* <p>
* The dictionary must contain only lower case values for the matching to work properly.
*
* @param fileName the name of the file
* @return the ranked dictionary (a {@code HashMap} which associated a
Expand Down
Loading

0 comments on commit 0bcab0c

Please sign in to comment.