Skip to content

Commit

Permalink
refactor: refactoring matchers.L33tMatcher
Browse files Browse the repository at this point in the history
  • Loading branch information
vvatanabe committed Aug 12, 2023
1 parent 95e3fb9 commit 0f62469
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 65 deletions.
15 changes: 0 additions & 15 deletions src/main/java/com/nulabinc/zxcvbn/matchers/BaseMatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,6 @@ protected List<Match> sorted(List<Match> matches) {
return matches;
}

protected CharSequence translate(CharSequence string, Map<Character, Character> chrMap) {
List<Character> characters = new ArrayList<>();
for (int n = 0; n < string.length(); n++) {
char chr = string.charAt(n);
characters.add(chrMap.containsKey(chr) ? chrMap.get(chr) : chr);
}
StringBuilder sb = new StringBuilder();
for (char c: characters) {
sb.append(c);
}
WipeableString result = new WipeableString(sb);
WipeableString.wipeIfPossible(sb);
return result;
}

private static class MatchComparator implements Comparator<Match>, Serializable {
private static final long serialVersionUID = 1L;

Expand Down
134 changes: 84 additions & 50 deletions src/main/java/com/nulabinc/zxcvbn/matchers/L33tMatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,56 @@

import com.nulabinc.zxcvbn.Context;
import com.nulabinc.zxcvbn.WipeableString;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

public class L33tMatcher extends BaseMatcher {

private final Map<String, Map<String, Integer>> rankedDictionaries;
private static final Map<Character, List<Character>> L33T_TABLE =
Collections.unmodifiableMap(
new HashMap<Character, List<Character>>() {
{
put('a', Arrays.asList('4', '@'));
put('b', Collections.singletonList('8'));
put('c', Arrays.asList('(', '{', '[', '<'));
put('e', Collections.singletonList('3'));
put('g', Arrays.asList('6', '9'));
put('i', Arrays.asList('1', '!', '|'));
put('l', Arrays.asList('1', '|', '7'));
put('o', Collections.singletonList('0'));
put('s', Arrays.asList('$', '5'));
put('t', Arrays.asList('+', '7'));
put('x', Collections.singletonList('%'));
put('z', Collections.singletonList('2'));
}
});

public L33tMatcher(Context context, Map<String, Map<String, Integer>> rankedDictionaries) {
super(context);
this.rankedDictionaries = rankedDictionaries;
}

private static final Map<Character, List<Character>> L33T_TABLE;

static {
Map<Character, List<Character>> l33tTable = new HashMap<>();
l33tTable.put('a', Arrays.asList('4', '@'));
l33tTable.put('b', Arrays.asList('8'));
l33tTable.put('c', Arrays.asList('(', '{', '[', '<'));
l33tTable.put('e', Arrays.asList('3'));
l33tTable.put('g', Arrays.asList('6', '9'));
l33tTable.put('i', Arrays.asList('1', '!', '|'));
l33tTable.put('l', Arrays.asList('1', '|', '7'));
l33tTable.put('o', Arrays.asList('0'));
l33tTable.put('s', Arrays.asList('$', '5'));
l33tTable.put('t', Arrays.asList('+', '7'));
l33tTable.put('x', Arrays.asList('%'));
l33tTable.put('z', Arrays.asList('2'));
L33T_TABLE = l33tTable;
}

public Map<Character, List<Character>> relevantL33tSubTable(CharSequence password) {
return relevantL33tSubTable(password, L33T_TABLE);
}

public Map<Character, List<Character>> relevantL33tSubTable(
CharSequence password, Map<Character, List<Character>> table) {
HashMap<Character, Boolean> passwordChars = new HashMap<>();
HashSet<Character> passwordChars = new HashSet<>();
for (int n = 0; n < password.length(); n++) {
passwordChars.put(password.charAt(n), true);
passwordChars.add(password.charAt(n));
}
Map<Character, List<Character>> subTable = new HashMap<>();
for (Map.Entry<Character, List<Character>> l33tRowRef : table.entrySet()) {
Character letter = l33tRowRef.getKey();
List<Character> subs = l33tRowRef.getValue();
List<Character> relevantSubs = new ArrayList<>();
for (Character sub : subs) {
if (passwordChars.containsKey(sub)) {
if (passwordChars.contains(sub)) {
relevantSubs.add(sub);
}
}
if (relevantSubs.size() > 0) {
if (!relevantSubs.isEmpty()) {
subTable.put(letter, relevantSubs);
}
}
Expand All @@ -68,33 +63,28 @@ public List<Match> execute(CharSequence password) {
List<Match> matches = new ArrayList<>();
Map<Character, List<Character>> subTable = relevantL33tSubTable(password);
L33tSubDict l33tSubs = new L33tSubDict(subTable);
DictionaryMatcher dictionaryMatcher =
new DictionaryMatcher(this.getContext(), rankedDictionaries);

for (Map<Character, Character> sub : l33tSubs) {
if (sub.isEmpty()) break;
CharSequence subbedPassword = translate(password, sub);
for (Match match :
new DictionaryMatcher(this.getContext(), rankedDictionaries).execute(subbedPassword)) {
if (sub.isEmpty()) {
break; // corner case: password has no relevant subs.
}
CharSequence subbedPassword = decodeL33tSpeak(password, sub);

for (Match match : dictionaryMatcher.execute(subbedPassword)) {

WipeableString token = WipeableString.copy(password, match.i, match.j + 1);
WipeableString lower = WipeableString.lowerCase(token);
if (lower.equals(match.matchedWord)) {
token.wipe();
lower.wipe();
continue;
}
Map<Character, Character> matchSub = new HashMap<>();
for (Map.Entry<Character, Character> subRef : sub.entrySet()) {
Character subbedChr = subRef.getKey();
Character chr = subRef.getValue();
if (token.indexOf(subbedChr) != -1) {
matchSub.put(subbedChr, chr);
}
}
List<String> subDisplays = new ArrayList<>();
for (Map.Entry<Character, Character> matchSubRef : matchSub.entrySet()) {
Character k = matchSubRef.getKey();
Character v = matchSubRef.getValue();
subDisplays.add(String.format("%s -> %s", k, v));
}
String subDisplay = Arrays.toString(subDisplays.toArray(new String[] {}));

Map<Character, Character> matchSub = extractMatchSub(token, sub);
String subDisplay = generateSubDisplay(matchSub);

matches.add(
MatchFactory.createDictionaryL33tMatch(
match.i,
Expand All @@ -110,8 +100,52 @@ public List<Match> execute(CharSequence password) {
lower.wipe();
}
}
List<Match> lst = new ArrayList<>();
for (Match match : matches) if (match.tokenLength() > 1) lst.add(match);
return this.sorted(lst);
return filterMatches(matches);
}

private Map<Character, Character> extractMatchSub(
WipeableString token, Map<Character, Character> sub) {
Map<Character, Character> matchSub = new HashMap<>();
for (Map.Entry<Character, Character> subRef : sub.entrySet()) {
Character subbedChr = subRef.getKey();
Character chr = subRef.getValue();
if (token.indexOf(subbedChr) != -1) {
matchSub.put(subbedChr, chr);
}
}
return matchSub;
}

private String generateSubDisplay(Map<Character, Character> matchSub) {
List<String> subDisplays = new ArrayList<>();
for (Map.Entry<Character, Character> matchSubRef : matchSub.entrySet()) {
Character k = matchSubRef.getKey();
Character v = matchSubRef.getValue();
subDisplays.add(String.format("%s -> %s", k, v));
}
return Arrays.toString(subDisplays.toArray(new String[0]));
}

private List<Match> filterMatches(List<Match> matches) {
List<Match> filteredMatches = new ArrayList<>();
for (Match match : matches) {
if (match.tokenLength() > 1) {
filteredMatches.add(match);
}
}
return this.sorted(filteredMatches);
}

private CharSequence decodeL33tSpeak(
CharSequence password, Map<Character, Character> l33tToRegularMapping) {
StringBuilder sb = new StringBuilder(password.length());
for (int charIndex = 0; charIndex < password.length(); charIndex++) {
char curChar = password.charAt(charIndex);
Character replacement = l33tToRegularMapping.get(curChar);
sb.append(replacement != null ? replacement : curChar);
}
WipeableString result = new WipeableString(sb);
WipeableString.wipeIfPossible(sb);
return result;
}
}

0 comments on commit 0f62469

Please sign in to comment.