Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement an "unmunger" (second attempt) #84

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
Expand Down
80 changes: 5 additions & 75 deletions src/main/java/me/gosimple/nbvcxz/matching/DictionaryMatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

import me.gosimple.nbvcxz.matching.match.DictionaryMatch;
import me.gosimple.nbvcxz.matching.match.Match;
import me.gosimple.nbvcxz.resources.Configuration;
import me.gosimple.nbvcxz.resources.*;
import me.gosimple.nbvcxz.resources.Dictionary;
import me.gosimple.nbvcxz.resources.DictionaryBuilder;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.TreeMap;
import java.util.*;

/**
* Look for every part of the password that match an entry in our dictionaries
Expand All @@ -18,74 +14,6 @@
*/
public final class DictionaryMatcher implements PasswordMatcher
{
/**
* Removes all leet substitutions from the password and returns a list of plain text versions.
*
* @param configuration the configuration file used to estimate entropy.
* @param password the password to translate from leet.
* @return a list of all combinations of possible leet translations for the password with all leet removed.
*/
private static List<String> translateLeet(final Configuration configuration, final String password)
{
final List<String> translations = new ArrayList();
final TreeMap<Integer, Character[]> replacements = new TreeMap<>();

for (int i = 0; i < password.length(); i++)
{
final Character[] replacement = configuration.getLeetTable().get(password.charAt(i));
if (replacement != null)
{
replacements.put(i, replacement);
}
}

// Do not bother continuing if we're going to replace every single character
if(replacements.keySet().size() == password.length())
return translations;

if (replacements.size() > 0)
{
final char[] password_char = new char[password.length()];
for (int i = 0; i < password.length(); i++)
{
password_char[i] = password.charAt(i);
}
replaceAtIndex(replacements, replacements.firstKey(), password_char, translations);
}

return translations;
}

/**
* Internal function to recursively build the list of un-leet possibilities.
*
* @param replacements TreeMap of replacement index, and the possible characters at that index to be replaced
* @param current_index internal use for the function
* @param password a Character array of the original password
* @param final_passwords List of the final passwords to be filled
*/
private static void replaceAtIndex(final TreeMap<Integer, Character[]> replacements, Integer current_index, final char[] password, final List<String> final_passwords)
{
for (final char replacement : replacements.get(current_index))
{
password[current_index] = replacement;
if (current_index.equals(replacements.lastKey()))
{
final_passwords.add(new String(password));
}
else if (final_passwords.size() > 100)
{
// Give up if we've already made 100 replacements
return;
}
else
{
replaceAtIndex(replacements, replacements.higherKey(current_index), password, final_passwords);
}
}
}


/**
* Gets the substitutions for the password.
*
Expand Down Expand Up @@ -279,6 +207,8 @@ public List<Match> match(final Configuration configuration, final String passwor

final List<Match> matches = new ArrayList<>();

final SubstitutionComboGen substitutionComboGen = new SubstitutionComboGen(configuration.getTrieNodeRoot());

// Create all possible sub-sequences of the password
for (int start = 0; start < password.length(); start++)
{
Expand Down Expand Up @@ -314,7 +244,7 @@ public List<Match> match(final Configuration configuration, final String passwor
// Only do unleet if it's different than the regular lower.
if (dictionary.getMaxLength() > split_password.length())
{
final List<String> unleet_list = translateLeet(configuration, lower_part);
final List<String> unleet_list = substitutionComboGen.getAllSubCombos(lower_part, configuration.getSubstituteComboLimit());
for (final String unleet_part : unleet_list)
{
final Integer unleet_rank = dictionary.getDictonary().get(unleet_part);
Expand Down
26 changes: 16 additions & 10 deletions src/main/java/me/gosimple/nbvcxz/resources/Configuration.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package me.gosimple.nbvcxz.resources;

import me.gosimple.nbvcxz.matching.DictionaryMatcher;
import me.gosimple.nbvcxz.matching.PasswordMatcher;
import me.gosimple.nbvcxz.matching.SpacialMatcher;
import me.gosimple.nbvcxz.matching.YearMatcher;
import me.gosimple.nbvcxz.matching.*;

import java.util.List;
import java.util.Locale;
Expand All @@ -22,10 +19,11 @@ public class Configuration
private final Map<String, Long> guessTypes;
private final List<Dictionary> dictionaries;
private final List<AdjacencyGraph> adjacencyGraphs;
private final Map<Character, Character[]> leetTable;
private final TrieNode trieNodeRoot;
private final Pattern yearPattern;
private final Double minimumEntropy;
private final Integer maxLength;
private final Integer substituteComboLimit;
private final Locale locale;
private final boolean distanceCalc;
private final ResourceBundle mainResource;
Expand All @@ -37,23 +35,24 @@ public class Configuration
* @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 trieNodeRoot Root trie node to help find possible string substitutions, 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 distanceCalc Enable or disable levenshtein distance calculation for dictionary matches
* @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, Integer maxLength, Locale locale, boolean distanceCalc, long combinationAlgorithmTimeout)
public Configuration(List<PasswordMatcher> passwordMatchers, Map<String, Long> guessTypes, List<Dictionary> dictionaries, List<AdjacencyGraph> adjacencyGraphs, TrieNode trieNodeRoot, Pattern yearPattern, Double minimumEntropy, Integer maxLength, Integer substituteComboLimit, Locale locale, boolean distanceCalc, long combinationAlgorithmTimeout)
{
this.passwordMatchers = passwordMatchers;
this.guessTypes = guessTypes;
this.dictionaries = dictionaries;
this.adjacencyGraphs = adjacencyGraphs;
this.leetTable = leetTable;
this.trieNodeRoot = trieNodeRoot;
this.yearPattern = yearPattern;
this.minimumEntropy = minimumEntropy;
this.maxLength = maxLength;
this.substituteComboLimit = substituteComboLimit;
this.locale = locale;
this.distanceCalc = distanceCalc;
this.mainResource = ResourceBundle.getBundle("main", locale);
Expand Down Expand Up @@ -96,9 +95,9 @@ public List<AdjacencyGraph> getAdjacencyGraphs()
/**
* @return Leet table for use with {@link DictionaryMatcher}
*/
public Map<Character, Character[]> getLeetTable()
public TrieNode getTrieNodeRoot()
{
return leetTable;
return trieNodeRoot;
}

/**
Expand All @@ -124,6 +123,13 @@ public Integer getMaxLength() {
return maxLength;
}

/**
* @return The default maximum number of string combos to generate, based on the possible string substitutions.
*/
public Integer getSubstituteComboLimit() {
return substituteComboLimit;
}

/**
* @return Locale for localized text and feedback
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class ConfigurationBuilder
private static final List<Dictionary> defaultDictionaries = new ArrayList<>();
private static final List<PasswordMatcher> defaultPasswordMatchers = new ArrayList<>();
private static final List<AdjacencyGraph> defaultAdjacencyGraphs = new ArrayList<>();
private static final Map<Character, Character[]> defaultLeetTable = new HashMap<>();
private static final TrieNode defaultTrieNodeRoot;

static
{
Expand All @@ -56,38 +56,59 @@ public class ConfigurationBuilder
defaultAdjacencyGraphs.add(new AdjacencyGraph("Standard Keypad", AdjacencyGraphUtil.standardKeypad));
defaultAdjacencyGraphs.add(new AdjacencyGraph("Mac Keypad", AdjacencyGraphUtil.macKeypad));

defaultLeetTable.put('4', new Character[]{'a'});
defaultLeetTable.put('@', new Character[]{'a'});
defaultLeetTable.put('8', new Character[]{'b'});
defaultLeetTable.put('(', new Character[]{'c'});
defaultLeetTable.put('{', new Character[]{'c'});
defaultLeetTable.put('[', new Character[]{'c'});
defaultLeetTable.put('<', new Character[]{'c'});
defaultLeetTable.put('3', new Character[]{'e'});
defaultLeetTable.put('9', new Character[]{'g'});
defaultLeetTable.put('6', new Character[]{'g'});
defaultLeetTable.put('&', new Character[]{'g'});
defaultLeetTable.put('#', new Character[]{'h'});
defaultLeetTable.put('!', new Character[]{'i', 'l'});
defaultLeetTable.put('1', new Character[]{'i', 'l'});
defaultLeetTable.put('|', new Character[]{'i', 'l'});
defaultLeetTable.put('0', new Character[]{'o'});
defaultLeetTable.put('$', new Character[]{'s'});
defaultLeetTable.put('5', new Character[]{'s'});
defaultLeetTable.put('+', new Character[]{'t'});
defaultLeetTable.put('7', new Character[]{'t', 'l'});
defaultLeetTable.put('%', new Character[]{'x'});
defaultLeetTable.put('2', new Character[]{'z'});
defaultTrieNodeRoot = new TrieNode()
// simple single character substitutions (mostly leet speak)
.addSub("4", "a")
.addSub("@", "a")
.addSub("8", "b")
.addSub("(", "c")
.addSub("{", "c")
.addSub("[", "c")
.addSub("<", "c", "k", "v")
.addSub(">", "v")
.addSub("3", "e")
.addSub("9", "g", "q")
.addSub("6", "d", "g")
.addSub("&", "g")
.addSub("#", "f", "h")
.addSub("!", "i", "l")
.addSub("1", "i", "l")
.addSub("|", "i", "l")
.addSub("0", "o")
.addSub("$", "s")
.addSub("5", "s")
.addSub("+", "t")
.addSub("7", "t", "l")
.addSub("%", "x")
.addSub("2", "z")
// extra "munged" variations from here: https://en.wikipedia.org/wiki/Munged_password
.addSub("?", "y") // (y = why?)
.addSub("uu", "w")
.addSub("vv", "w")
.addSub("nn", "m")
.addSub("2u", "uu", "w")
.addSub("2v", "vv", "w")
.addSub("2n", "nn", "m")
.addSub("2b", "bb")
.addSub("2d", "dd")
.addSub("2g", "gg")
.addSub("2l", "ll")
.addSub("2p", "pp")
.addSub("2t", "tt")
.addSub("\\/\\/", "w")
.addSub("/\\/\\", "m")
.addSub("|)", "d");
}

private List<PasswordMatcher> passwordMatchers;
private Map<String, Long> guessTypes;
private List<Dictionary> dictionaries;
private List<AdjacencyGraph> adjacencyGraphs;
private Map<Character, Character[]> leetTable;
private TrieNode trieNodeRoot;
private Pattern yearPattern;
private Double minimumEntropy;
private Integer maxLength;
private Integer substituteComboLimit;
private Locale locale;
private Boolean distanceCalc;
private Long combinationAlgorithmTimeout;
Expand Down Expand Up @@ -180,11 +201,11 @@ public static List<AdjacencyGraph> getDefaultAdjacencyGraphs()
}

/**
* @return The default table of common english leet substitutions
* @return The default trie node root to find string substitutions
*/
public static Map<Character, Character[]> getDefaultLeetTable()
public static TrieNode getTrieNodeRoot()
{
return new HashMap<>(defaultLeetTable);
return defaultTrieNodeRoot;
}

/**
Expand Down Expand Up @@ -212,6 +233,14 @@ public static int getDefaultMaxLength()
return 256;
}

/**
* @return The default maximum number of password substitutions combos to generate, for a given password
*/
public static int getSubstituteComboLimit()
{
return 250;
}

/**
* @return the default is false
*/
Expand Down Expand Up @@ -289,12 +318,12 @@ public ConfigurationBuilder setAdjacencyGraphs(List<AdjacencyGraph> adjacencyGra
/**
* The leet table is used to check within a password for common character substitutions (e.g. s to $).
*
* @param leetTable Map for leetTable
* @param trieNodeRoot Map for leetTable
* @return Builder
*/
public ConfigurationBuilder setLeetTable(Map<Character, Character[]> leetTable)
public ConfigurationBuilder setLeetTable(TrieNode trieNodeRoot)
{
this.leetTable = leetTable;
this.trieNodeRoot = trieNodeRoot;
return this;
}

Expand Down Expand Up @@ -445,9 +474,9 @@ public Configuration createConfiguration()
{
adjacencyGraphs = getDefaultAdjacencyGraphs();
}
if (leetTable == null)
if (trieNodeRoot == null)
{
leetTable = getDefaultLeetTable();
trieNodeRoot = getTrieNodeRoot();
}
if (yearPattern == null)
{
Expand All @@ -461,6 +490,9 @@ public Configuration createConfiguration()
{
maxLength = getDefaultMaxLength();
}
if (substituteComboLimit == null) {
substituteComboLimit = getSubstituteComboLimit();
}
if (locale == null)
{
locale = Locale.getDefault();
Expand All @@ -473,7 +505,7 @@ public Configuration createConfiguration()
{
combinationAlgorithmTimeout = getDefaultCombinationAlgorithmTimeout();
}
return new Configuration(passwordMatchers, guessTypes, dictionaries, adjacencyGraphs, leetTable, yearPattern, minimumEntropy, maxLength, locale, distanceCalc, combinationAlgorithmTimeout);
return new Configuration(passwordMatchers, guessTypes, dictionaries, adjacencyGraphs, trieNodeRoot, yearPattern, minimumEntropy, maxLength, substituteComboLimit, locale, distanceCalc, combinationAlgorithmTimeout);
}


Expand Down
Loading