diff --git a/README.md b/README.md index 8a446be..736b47d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ - All transformations support multibyte characters. ``` -Usage of Password Transformation Tool (ptt) version (0.2.0): +Usage of Password Transformation Tool (ptt) version (0.2.1): ptt [options] [...] Accepts standard input and/or additonal arguments. @@ -48,6 +48,8 @@ Options: Show statistics output when possible. -vvv Show verbose statistics output when possible. + -w int + Number of words to generate for passphrases if applicable. The -f, -k, -r, -tf, -tp, and -u flags can be used multiple times and together. @@ -78,6 +80,8 @@ Transformation Modes: Transforms input by swapping tokens from a partial mask file and a input file. -t overwrite -i [index] Transforms input into overwrite rules starting at index. + -t passphrase -w [words] -tf [file] + Transforms input by randomly generating passphrases with a given number of words and separators from a file. -t pop -rm [uldsb] Transforms input by generating tokens from popping strings at character boundaries. -t prepend diff --git a/docs/USAGE.md b/docs/USAGE.md index 92b3f3d..18a86c8 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -122,6 +122,7 @@ The following transformations can be used with the `-t` flag: - `swap`: Transforms input by swapping tokens with exact matches from a ':' separated file. - `pop`: Transforms input by generating tokens from popping strings at character boundaries. - `mask-swap`: Transforms input by swapping tokens from a partial mask file and a input file. +- `passphrase`: Transforms input by randomly generating passphrases with a given number of words and separators from a file. The modes also have aliases that can be used with the `-t` flag instead of the keywords above: @@ -146,6 +147,7 @@ keywords above: - `swap`: `s`, `replace` - `pop`: `po`, `split`, `boundary-split`, `boundary-pop`, `pop-split`, `split-pop` - `mask-swap`: `ms`, `shuf`, `shuffle`, `token-swap` +- `passphrase`: `pp`, `phrase` ### Examples diff --git a/docs/WORDLISTS.md b/docs/WORDLISTS.md index a046439..67b97e9 100644 --- a/docs/WORDLISTS.md +++ b/docs/WORDLISTS.md @@ -17,6 +17,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. All modes support multibyte characters and can properly convert them. One transformation can be used at a time. @@ -70,3 +72,17 @@ This mode is most similar to token-swapping in that it generates new 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: +``` +ptt -f -t passphrase -w -tf +``` + +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. diff --git a/main.go b/main.go index 9641e8e..cdf53e4 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,7 @@ import ( "github.com/jakewnuk/ptt/pkg/utils" ) -var version = "0.2.0" +var version = "0.2.1" var wg sync.WaitGroup var mutex = &sync.Mutex{} var retain models.FileArgumentFlag @@ -59,6 +59,7 @@ func main() { "mask-match -tf [file]": "Transforms input by keeping only strings with matching masks from a mask file.", "swap -tf [file]": "Transforms input by swapping tokens 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.", } // Sort and print transformation modes @@ -85,6 +86,7 @@ func main() { jsonOutput := flag.String("o", "", "Output to JSON file in addition to stdout.") bypassMap := flag.Bool("b", false, "Bypass map creation and use stdout as primary output.") debugMode := flag.Int("d", 0, "Enable debug mode with verbosity levels [0-2].") + passPhraseWords := flag.Int("w", 0, "Number of words to generate for passphrases if applicable.") flag.Var(&retain, "k", "Only keep items in a file.") flag.Var(&remove, "r", "Only keep items not in a file.") flag.Var(&readFiles, "f", "Read additional files for input.") @@ -135,7 +137,7 @@ func main() { // Apply transformation if provided if *transformation != "" && templateFiles == nil { - primaryMap = transform.TransformationController(primaryMap, *transformation, intRange.Start, intRange.End, *verbose, *replacementMask, transformationFilesMap, *bypassMap, *debugMode) + primaryMap = transform.TransformationController(primaryMap, *transformation, intRange.Start, intRange.End, *verbose, *replacementMask, transformationFilesMap, *bypassMap, *debugMode, *passPhraseWords) } else if templateFiles != nil && *transformation == "" { fmt.Fprintf(os.Stderr, "[*] Using template files for multiple transformations.\n") @@ -148,9 +150,9 @@ func main() { // Apply transformations from template files for i, template := range transformationTemplateArray { if i == 0 { - temporaryMap = transform.TransformationController(primaryMap, template.TransformationMode, template.StartIndex, template.EndIndex, template.Verbose, template.ReplacementMask, transformationFilesMap, template.Bypass, *debugMode) + temporaryMap = transform.TransformationController(primaryMap, template.TransformationMode, template.StartIndex, template.EndIndex, template.Verbose, template.ReplacementMask, transformationFilesMap, template.Bypass, *debugMode, *passPhraseWords) } else { - temporaryMap = utils.CombineMaps(temporaryMap, transform.TransformationController(primaryMap, template.TransformationMode, template.StartIndex, template.EndIndex, template.Verbose, template.ReplacementMask, transformationFilesMap, template.Bypass, *debugMode)) + temporaryMap = utils.CombineMaps(temporaryMap, transform.TransformationController(primaryMap, template.TransformationMode, template.StartIndex, template.EndIndex, template.Verbose, template.ReplacementMask, transformationFilesMap, template.Bypass, *debugMode, *passPhraseWords)) } } primaryMap = temporaryMap diff --git a/pkg/transform/transform.go b/pkg/transform/transform.go index 30518b4..49c2084 100644 --- a/pkg/transform/transform.go +++ b/pkg/transform/transform.go @@ -3,6 +3,7 @@ package transform import ( "fmt" + "math/rand" "os" "github.com/jakewnuk/ptt/pkg/format" @@ -31,11 +32,12 @@ import ( // use for modes like retain-mask // bypass (bool): If true, the map is not used for output or filtering // debug (int): Different debug levels to use for debugging [0-2] +// passphraseWord (int): The number of words to use for passphrase generation // // Returns: // // (map[string]int): A map of transformed values -func TransformationController(input map[string]int, mode string, startingIndex int, endingIndex int, verbose bool, replacementMask string, transformationFilesMap map[string]int, bypass bool, debug int) (output map[string]int) { +func TransformationController(input map[string]int, mode string, startingIndex int, endingIndex int, verbose bool, replacementMask string, transformationFilesMap map[string]int, bypass bool, debug int, passphraseWord int) (output map[string]int) { functionDebug := false if debug > 1 { @@ -113,6 +115,12 @@ func TransformationController(input map[string]int, mode string, startingIndex i os.Exit(1) } output = mask.ShuffleMap(input, replacementMask, transformationFilesMap, bypass, functionDebug) + case "passphrase", "phrase", "pp": + if passphraseWord == 0 { + 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, passphraseWord) default: output = input } @@ -163,3 +171,97 @@ func ReplaceKeysInMap(originalMap map[string]int, replacements map[string]int, b } return newMap } + +// 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. +// +// 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 +// passphraseWord (int): The number of words to use for passphrase generation +// +// 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 { + newMap := make(map[string]int) + + for key, value := range input { + + 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) + } + + if !bypass { + newMap[newKeyPhrase] = value + } else { + fmt.Println(newKeyPhrase) + } + } + } + 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 +} diff --git a/pkg/transform/transform_test.go b/pkg/transform/transform_test.go index 57d0290..be60377 100644 --- a/pkg/transform/transform_test.go +++ b/pkg/transform/transform_test.go @@ -16,7 +16,8 @@ import ( // Functions without Unit Tests // ---------------------------------------------------------------------------- // - TransformationController (TransformationController) -// - +// - MakePassphraseMap (Generation Functions) +// - GeneratePassphrase (Generation Functions) // Unit Test for ReplaceKeysInMap func TestReplaceKeysInMap(t *testing.T) {