Marvin Preuss
1d4ae27878
All checks were successful
continuous-integration/drone/push Build is passing
235 lines
7.4 KiB
Go
235 lines
7.4 KiB
Go
package matching
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/nbutton23/zxcvbn-go/entropy"
|
|
"github.com/nbutton23/zxcvbn-go/match"
|
|
)
|
|
|
|
// L33TMatcherName id
|
|
const L33TMatcherName = "l33t"
|
|
|
|
//FilterL33tMatcher can be pass to zxcvbn-go.PasswordStrength to skip that matcher
|
|
func FilterL33tMatcher(m match.Matcher) bool {
|
|
return m.ID == L33TMatcherName
|
|
}
|
|
|
|
func l33tMatch(password string) []match.Match {
|
|
permutations := getPermutations(password)
|
|
|
|
var matches []match.Match
|
|
|
|
for _, permutation := range permutations {
|
|
for _, mather := range dictionaryMatchers {
|
|
matches = append(matches, mather.MatchingFunc(permutation)...)
|
|
}
|
|
}
|
|
|
|
for _, match := range matches {
|
|
match.Entropy += entropy.ExtraLeetEntropy(match, password)
|
|
match.DictionaryName = match.DictionaryName + "_3117"
|
|
}
|
|
|
|
return matches
|
|
}
|
|
|
|
// This function creates a list of permutations based on a fixed table stored on data. The table
|
|
// will be reduced in order to proceed in the function using only relevant values (see
|
|
// relevantL33tSubtable).
|
|
func getPermutations(password string) []string {
|
|
substitutions := relevantL33tSubtable(password)
|
|
permutations := getAllPermutationsOfLeetSubstitutions(password, substitutions)
|
|
return permutations
|
|
}
|
|
|
|
// This function loads the table from data but only keep in memory the values that are present
|
|
// inside the provided password.
|
|
func relevantL33tSubtable(password string) map[string][]string {
|
|
relevantSubs := make(map[string][]string)
|
|
for key, values := range l33tTable.Graph {
|
|
for _, value := range values {
|
|
if strings.Contains(password, value) {
|
|
relevantSubs[key] = append(relevantSubs[key], value)
|
|
}
|
|
}
|
|
}
|
|
|
|
return relevantSubs
|
|
}
|
|
|
|
// This function creates the list of permutations of a given password using the provided table as
|
|
// reference for its operation.
|
|
func getAllPermutationsOfLeetSubstitutions(password string, table map[string][]string) []string {
|
|
result := []string{}
|
|
|
|
// create a list of tables without conflicting keys/values (this happens for "|", "7" and "1")
|
|
noConflictsTables := createListOfMapsWithoutConflicts(table)
|
|
for _, noConflictsTable := range noConflictsTables {
|
|
substitutionsMaps := createSubstitutionsMapsFromTable(noConflictsTable)
|
|
for _, substitutionsMap := range substitutionsMaps {
|
|
newValue := createWordForSubstitutionMap(password, substitutionsMap)
|
|
if !stringSliceContainsValue(result, newValue) {
|
|
result = append(result, newValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Create the possible list of maps removing the conflicts from it. As an example, the value "|"
|
|
// may represent "i" and "l". For each representation of the conflicting value, a new map is
|
|
// created. This may grow exponencialy according to the number of conflicts. The number of maps
|
|
// returned by this function may be reduced if the relevantL33tSubtable function was called to
|
|
// identify only relevant items.
|
|
func createListOfMapsWithoutConflicts(table map[string][]string) []map[string][]string {
|
|
// the resulting list starts with the provided table
|
|
result := []map[string][]string{}
|
|
result = append(result, table)
|
|
|
|
// iterate over the list of conflicts in order to expand the maps for each one
|
|
conflicts := retrieveConflictsListFromTable(table)
|
|
for _, value := range conflicts {
|
|
newMapList := []map[string][]string{}
|
|
|
|
// for each conflict a new list of maps will be created for every already known map
|
|
for _, currentMap := range result {
|
|
newMaps := createDifferentMapsForLeetChar(currentMap, value)
|
|
newMapList = append(newMapList, newMaps...)
|
|
}
|
|
|
|
result = newMapList
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// This function retrieves the list of values that appear for one or more keys. This is usefull to
|
|
// know which l33t chars can represent more than one letter.
|
|
func retrieveConflictsListFromTable(table map[string][]string) []string {
|
|
result := []string{}
|
|
foundValues := []string{}
|
|
|
|
for _, values := range table {
|
|
for _, value := range values {
|
|
if stringSliceContainsValue(foundValues, value) {
|
|
// only add on results if it was not identified as conflict before
|
|
if !stringSliceContainsValue(result, value) {
|
|
result = append(result, value)
|
|
}
|
|
} else {
|
|
foundValues = append(foundValues, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// This function aims to create different maps for a given char if this char represents a conflict.
|
|
// If the specified char is not a conflit one, the same map will be returned. In scenarios which
|
|
// the provided char can not be found on map, an empty list will be returned. This function was
|
|
// designed to be used on conflicts situations.
|
|
func createDifferentMapsForLeetChar(table map[string][]string, leetChar string) []map[string][]string {
|
|
result := []map[string][]string{}
|
|
|
|
keysWithSameValue := retrieveListOfKeysWithSpecificValueFromTable(table, leetChar)
|
|
for _, key := range keysWithSameValue {
|
|
newMap := copyMapRemovingSameValueFromOtherKeys(table, key, leetChar)
|
|
result = append(result, newMap)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// This function retrieves the list of keys that can be represented using the given value.
|
|
func retrieveListOfKeysWithSpecificValueFromTable(table map[string][]string, valueToFind string) []string {
|
|
result := []string{}
|
|
|
|
for key, values := range table {
|
|
for _, value := range values {
|
|
if value == valueToFind && !stringSliceContainsValue(result, key) {
|
|
result = append(result, key)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// This function returns a lsit of substitution map from a given table. Each map in the result will
|
|
// provide only one representation for each value. As an example, if the provided map contains the
|
|
// values "@" and "4" in the possibilities to represent "a", two maps will be created where one
|
|
// will contain "a" mapping to "@" and the other one will provide "a" mapping to "4".
|
|
func createSubstitutionsMapsFromTable(table map[string][]string) []map[string]string {
|
|
result := []map[string]string{{"": ""}}
|
|
|
|
for key, values := range table {
|
|
newResult := []map[string]string{}
|
|
|
|
for _, mapInCurrentResult := range result {
|
|
for _, value := range values {
|
|
newMapForValue := copyMap(mapInCurrentResult)
|
|
newMapForValue[key] = value
|
|
newResult = append(newResult, newMapForValue)
|
|
}
|
|
}
|
|
|
|
result = newResult
|
|
}
|
|
|
|
// verification to make sure that the slice was filled
|
|
if len(result) == 1 && len(result[0]) == 1 && result[0][""] == "" {
|
|
return []map[string]string{}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// This function replaces the values provided on substitution map over the provided word.
|
|
func createWordForSubstitutionMap(word string, substitutionMap map[string]string) string {
|
|
result := word
|
|
for key, value := range substitutionMap {
|
|
result = strings.Replace(result, value, key, -1)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func stringSliceContainsValue(slice []string, value string) bool {
|
|
for _, valueInSlice := range slice {
|
|
if valueInSlice == value {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func copyMap(table map[string]string) map[string]string {
|
|
result := make(map[string]string)
|
|
|
|
for key, value := range table {
|
|
result[key] = value
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// This function creates a new map based on the one provided but excluding possible representations
|
|
// of the same value on other keys.
|
|
func copyMapRemovingSameValueFromOtherKeys(table map[string][]string, keyToFix string, valueToFix string) map[string][]string {
|
|
result := make(map[string][]string)
|
|
|
|
for key, values := range table {
|
|
for _, value := range values {
|
|
if !(value == valueToFix && key != keyToFix) {
|
|
result[key] = append(result[key], value)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|