From db62f53d63258dce81d8aa3868440b4cacd9c918 Mon Sep 17 00:00:00 2001 From: Jake Wnuk Date: Thu, 19 Sep 2024 15:47:03 -0400 Subject: [PATCH] Redo passphrase transformation - overhauled the passphrase transformation mode to be simpler and cover more hard to hit patterns --- README.md | 6 +-- docs/USAGE.md | 25 +++++-------- go.mod | 5 ++- go.sum | 2 + main.go | 4 +- pkg/transform/transform.go | 76 ++++---------------------------------- pkg/utils/utils.go | 62 +++++++++++++++++++++++++++++++ pkg/utils/utils_test.go | 31 ++++++++++++++++ 8 files changed, 122 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 723228b..7fe5365 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ git clone https://github.com/JakeWnuk/ptt && cd ptt && docker build -t ptt . && ### Usage: ``` -Usage of Password Transformation Tool (ptt) version (0.3.3): +Usage of Password Transformation Tool (ptt) version (0.3.4): ptt [options] [...] Accepts standard input and/or additonal arguments. @@ -112,8 +112,8 @@ These create or alter based on the selected mode. Transforms input by creating masks that still retain strings from file. -t mask-swap -tf [file] Transforms input by swapping tokens from a partial mask file and a input file. - -t passphrase -w [words] -tf [file] - Transforms input by randomly generating passphrases with a given number of words and separators from a file. + -t passphrase -w [words] + Transforms input by generating passphrases from sentences with a given number of words. -t regram -w [words] Transforms input by 'regramming' sentences into new n-grams with a given number of words. -t replace-all -tf [file] diff --git a/docs/USAGE.md b/docs/USAGE.md index 1f66aed..e0f2872 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -177,8 +177,8 @@ The following transformations can be used with the `-t` flag: Transforms input by creating masks that still retain strings from file. -t mask-swap -tf [file] Transforms input by swapping tokens from a partial mask file and a input file. - -t passphrase -w [words] -tf [file] - Transforms input by randomly generating passphrases with a given number of words and separators from a file. + -t passphrase -w [words] + Transforms input by generating passphrases from sentences with a given number of words. -t regram -w [words] Transforms input by 'regramming' sentences into new n-grams with a given number of words. -t replace-all -tf [file] @@ -525,8 +525,8 @@ wordlists. There are several ways to generate wordlists using PTT: This is implemented in the `pop` module. - `Token Swapping`: Generates tokens by swapping characters in a string. This is implemented in the `mask-swap` module. -- `Passphrases`: Generates passphrases by combining words from a wordlist. This - is implemented in the `passphrase` module. +- `Passphrases`: Generates passphrases by reforming setences. This is implemented + in the `passphrase` module. All modes support multibyte characters and can properly convert them. One transformation can be used at a time. @@ -593,19 +593,14 @@ candidates by using masks. However, it is unique in that it uses partial masks to limit the swap positions from prior applications. ### Passphrases -The `passphrase` module generates passphrases by combining words from a wordlist. -The `-w` flag can be used to specify the number of words to use in the passphrase. -The `-tf` flag is optional and can be used to specify a file containing separators -to use between words. The syntax is as follows: +The `passphrase` module generates passphrases by reforming sentences. The syntax is as follows: ``` -ptt -f -t passphrase -w -tf +ptt -f -t passphrase -w ``` - -The passphrases are generated randomly by selecting words and separators from the input. -If no separator file is provided, no separators will be used. The default word count is 0. -The number of passphrases generated is equal to the number of lines in the input file -*including* duplicates. This means that the item count is also used to determine the number -of passphrases generated. +The `passphrase` mode will generate new passphrases from the input by +reformatting the sentences into new passphrases. The number of words to use in +the passphrase is specified by the `-w` flag. The output will be the new +passphrases generated from the input with the specified word count. ## Misc Creation Guide diff --git a/go.mod b/go.mod index e1912e4..0dc659f 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/jakewnuk/ptt go 1.23.0 -require golang.org/x/net v0.24.0 +require ( + golang.org/x/net v0.24.0 + golang.org/x/text v0.14.0 +) diff --git a/go.sum b/go.sum index 4a8ba20..cfce262 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/main.go b/main.go index a255e3b..202096e 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,7 @@ import ( "github.com/jakewnuk/ptt/pkg/utils" ) -var version = "0.3.3" +var version = "0.3.4" var wg sync.WaitGroup var mutex = &sync.Mutex{} var retain models.FileArgumentFlag @@ -62,7 +62,7 @@ func main() { "mask-match -tf [file]": "Transforms input by keeping only strings with matching masks from a mask file.", "swap-single -tf [file]": "Transforms input by swapping tokens once per string per replacement with exact matches from a ':' separated file.", "mask-swap -tf [file]": "Transforms input by swapping tokens from a partial mask file and a input file.", - "passphrase -w [words] -tf [file]": "Transforms input by randomly generating passphrases with a given number of words and separators from a file.", + "passphrase -w [words]": "Transforms input by generating passphrases from sentences with a given number of words.", "substring -i [index]": "Transforms input by extracting substrings starting at index and ending at index.", "replace-all -tf [file]": "Transforms input by replacing all strings with all matches from a ':' separated file.", "regram -w [words]": "Transforms input by 'regramming' sentences into new n-grams with a given number of words.", diff --git a/pkg/transform/transform.go b/pkg/transform/transform.go index 1df2b0b..564ed0b 100644 --- a/pkg/transform/transform.go +++ b/pkg/transform/transform.go @@ -3,7 +3,6 @@ package transform import ( "fmt" - "math/rand" "os" "strings" @@ -121,7 +120,7 @@ func TransformationController(input map[string]int, mode string, startingIndex i fmt.Fprintf(os.Stderr, "[!] Passphrase operations require use of the -w flag to specify the number of words to use\n") os.Exit(1) } - output = MakePassphraseMap(input, transformationFilesMap, bypass, functionDebug, passphraseWords) + output = MakePassphraseMap(input, bypass, functionDebug, passphraseWords) case "substring": output = utils.SubstringMap(input, startingIndex, endingIndex, bypass, functionDebug) case "replace-all", "replace": @@ -223,15 +222,11 @@ func ReplaceAllKeysInMap(originalMap map[string]int, replacements map[string]int } // MakePassphraseMap takes a map of keys and creates a new map with new -// passphrases for each key. The transformation file is used to insert -// separators between the words. If the replacement mask is set to blank, then -// the words are concatenated together without any separators. Passphrases are -// generated by selecting a random word from the transformation file for each key. +// passphrases for each key. // // Args: // // input (map[string]int): The original map to replace keys in -// transformationFilesMap (map[string]int): A map of transformation files to // use for constructing the passphrases // bypass (bool): If true, the map is not used for output or filtering // debug (bool): If true, print additional debug information to stderr @@ -240,82 +235,27 @@ func ReplaceAllKeysInMap(originalMap map[string]int, replacements map[string]int // Returns: // // (map[string]int): A new map with the keys replaced -func MakePassphraseMap(input map[string]int, transformationFilesMap map[string]int, bypass bool, debug bool, passphraseWord int) map[string]int { +func MakePassphraseMap(input map[string]int, bypass bool, debug bool, passphraseWord int) map[string]int { newMap := make(map[string]int) - for key, value := range input { + newKeyArray := utils.GeneratePassphrase(key, passphraseWord) + for _, newKey := range newKeyArray { - for i := 0; i < value; i++ { - newKeyPhrase := GeneratePassphrase(input, transformationFilesMap, passphraseWord) if debug { fmt.Fprintf(os.Stderr, "Key: %s\n", key) - fmt.Fprintf(os.Stderr, "New Phrase: %s\n", newKeyPhrase) + fmt.Fprintf(os.Stderr, "New Key: %s\n", newKey) } if !bypass { - newMap[newKeyPhrase] = value + newMap[newKey] = value } else { - fmt.Println(newKeyPhrase) + fmt.Println(newKey) } } } return newMap } -// GeneratePassphrase takes a key and a map of transformation files and -// generates a passphrase based on the number of words specified. The words -// are selected from the transformation files and concatenated together with -// a separator. If the replacement mask is set to blank, then the words are -// concatenated together without any separators. -// -// Args: -// -// passWords (map[string]int): Content of the passphrase for use as words in -// the passphrase -// transformationFilesMap (map[string]int): Content of the transformation -// files for use as separators between words -// passphraseWord (int): The number of words to use for passphrase generation -// -// Returns: -// -// (string): The generated passphrase -func GeneratePassphrase(passWords map[string]int, transformationFilesMap map[string]int, passphraseWord int) string { - words := make([]string, passphraseWord) - - seps := make([]string, 0, len(transformationFilesMap)) - for k := range transformationFilesMap { - seps = append(seps, k) - } - - if len(seps) == 0 { - seps = append(seps, "") - } - - keys := make([]string, 0, len(passWords)) - for k := range passWords { - keys = append(keys, k) - } - - for i := 0; i < passphraseWord; i++ { - sep := seps[rand.Intn(len(seps))] - key := keys[rand.Intn(len(keys))] - - if i+1 >= passphraseWord { - words[i] = fmt.Sprintf("%s%s", key, "") - } else { - words[i] = fmt.Sprintf("%s%s", key, sep) - } - - } - - var newKeyPhrase string - for _, word := range words { - newKeyPhrase += word - } - - return newKeyPhrase -} - // GenerateNGramMap takes a map of keys and values and generates a new map // using the utils.GenerateNGrams function and combines the results. This // function is used to generate n-grams from the input map for the regram diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 5cf0d99..ebffb59 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -22,6 +22,8 @@ import ( "github.com/jakewnuk/ptt/pkg/models" "golang.org/x/net/html" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) // ---------------------------------------------------------------------------- @@ -978,6 +980,66 @@ func GenerateNGrams(text string, n int) []string { return nGrams } +func GeneratePassphrase(text string, n int) []string { + words := strings.Fields(text) + var passphrases []string + + if len(words) != n { + return passphrases + } + + var titleCaseWords string + var turkTitleCaseWords string + var CAPSlowerWords []string + var lowerCAPSWords []string + var lowerl33tWords []string + var l33tlowerWords []string + var CAPSl33tWords []string + var l33tCAPSWords []string + tick := false + titleCaseWords = cases.Title(language.Und, cases.NoLower).String(text) + turkTitleCaseWords = cases.Upper(language.Turkish, cases.NoLower).String(text) + + for _, word := range words { + + if tick { + CAPSlowerWords = append(CAPSlowerWords, strings.ToUpper(word)) + lowerCAPSWords = append(lowerCAPSWords, strings.ToLower(word)) + lowerl33tWords = append(lowerl33tWords, strings.ToLower(word)) + l33tlowerWords = append(l33tlowerWords, strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(word, "a", "4"), "e", "3"))) + CAPSl33tWords = append(CAPSl33tWords, strings.ToUpper(word)) + l33tCAPSWords = append(l33tCAPSWords, strings.ReplaceAll(strings.ReplaceAll(word, "a", "4"), "e", "3")) + } else { + CAPSlowerWords = append(CAPSlowerWords, strings.ToLower(word)) + lowerCAPSWords = append(lowerCAPSWords, strings.ToUpper(word)) + lowerl33tWords = append(lowerl33tWords, strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(word, "a", "4"), "e", "3"))) + l33tlowerWords = append(l33tlowerWords, strings.ToLower(word)) + CAPSl33tWords = append(CAPSl33tWords, strings.ReplaceAll(strings.ReplaceAll(word, "a", "4"), "e", "3")) + l33tCAPSWords = append(l33tCAPSWords, strings.ToUpper(word)) + } + + tick = !tick + + } + CAPSlowerPassphrase := strings.Join(CAPSlowerWords, "") + lowerCAPSPassphrase := strings.Join(lowerCAPSWords, "") + lowerl33tPassphrase := strings.Join(lowerl33tWords, "") + l33tlowerPassphrase := strings.Join(l33tlowerWords, "") + CAPSl33tPassphrase := strings.Join(CAPSl33tWords, "") + l33tCAPSPassphrase := strings.Join(l33tCAPSWords, "") + + passphrases = append(passphrases, strings.ReplaceAll(titleCaseWords, " ", "")) + passphrases = append(passphrases, strings.ReplaceAll(turkTitleCaseWords, " ", "")) + passphrases = append(passphrases, CAPSlowerPassphrase) + passphrases = append(passphrases, lowerCAPSPassphrase) + passphrases = append(passphrases, lowerl33tPassphrase) + passphrases = append(passphrases, l33tlowerPassphrase) + passphrases = append(passphrases, CAPSl33tPassphrase) + passphrases = append(passphrases, l33tCAPSPassphrase) + + return passphrases +} + // ---------------------------------------------------------------------------- // Validation Functions // ---------------------------------------------------------------------------- diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index f32a099..ab8f73e 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -25,6 +25,7 @@ import ( // - ReplaceAllSubstring() // - SubstringMap() // - GenerateNGrams() +// - GeneratePassphrase() // // ** Validation Functions ** // - CheckASCIIString() @@ -659,6 +660,36 @@ func TestGenerateNGrams(t *testing.T) { } } +// Unit Test for GeneratePassphrase() +func TestGeneratePassphrase(t *testing.T) { + + // Define a test case struct + type TestCase struct { + Input1 string + Input2 int + Output []string + } + + type TestCases []TestCase + + // Define test cases + testCases := TestCases{ + {"I <3 you", 3, []string{"I<3you", "I<3YOU", "i<3you", "I<3YOU", "I<3You", "i<3you", "i<3you", "I<3YOU"}}, + } + + // Run test cases + for _, testCase := range testCases { + input1 := testCase.Input1 + input2 := testCase.Input2 + output := testCase.Output + + given := GeneratePassphrase(input1, input2) + if CheckAreArraysEqual(given, output) == false { + t.Errorf("GeneratePassphrase(%v, %v) = %v; want %v", input1, input2, given, output) + } + } +} + // Unit Test for IsValidURL() func TestIsValidURL(t *testing.T) {