diff --git a/algo.go b/algo.go index 63fb0db..972de82 100644 --- a/algo.go +++ b/algo.go @@ -5,9 +5,9 @@ package apg type Algorithm int const ( - // AlgoPronouncable represents the algorithm for pronouncable passwords + // AlgoPronounceable represents the algorithm for pronounceable passwords // (koremutake syllables) - AlgoPronouncable Algorithm = iota + AlgoPronounceable Algorithm = iota // AlgoRandom represents the algorithm for purely random passwords according // to the provided password modes/flags AlgoRandom @@ -23,7 +23,7 @@ const ( func IntToAlgo(a int) Algorithm { switch a { case 0: - return AlgoPronouncable + return AlgoPronounceable case 1: return AlgoRandom case 2: diff --git a/algo_test.go b/algo_test.go index bab7c4a..5980530 100644 --- a/algo_test.go +++ b/algo_test.go @@ -8,7 +8,7 @@ func TestIntToAlgo(t *testing.T) { a int e Algorithm }{ - {"AlgoPronouncable", 0, AlgoPronouncable}, + {"AlgoPronounceable", 0, AlgoPronounceable}, {"AlgoRandom", 1, AlgoRandom}, {"AlgoCoinflip", 2, AlgoCoinFlip}, {"AlgoUnsupported", 3, AlgoUnsupported}, diff --git a/apg.go b/apg.go index a3d9982..7fd7830 100644 --- a/apg.go +++ b/apg.go @@ -7,6 +7,9 @@ const VERSION = "2.0.0" type Generator struct { // config is a pointer to the apg config instance config *Config + // syllables holds the single syllables of the lasst generated + // pronounceable password + syllables []string } // New returns a new password Generator type diff --git a/cmd/apg/apg.go b/cmd/apg/apg.go index 16e6996..9ec210b 100644 --- a/cmd/apg/apg.go +++ b/cmd/apg/apg.go @@ -42,6 +42,7 @@ func main() { flag.StringVar(&modeString, "M", "", "") flag.Int64Var(&config.NumberPass, "n", config.NumberPass, "") flag.BoolVar(&config.SpellPassword, "l", false, "") + flag.BoolVar(&config.SpellPronounceable, "t", false, "") flag.Usage = usage flag.Parse() @@ -114,7 +115,7 @@ func main() { _, _ = fmt.Fprintf(os.Stderr, "failed to generate password: %s\n", err) os.Exit(1) } - if config.SpellPassword { + if config.Algorithm == apg.AlgoRandom && config.SpellPassword { spellPass, err := apg.Spell(password) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "failed to spell password: %s\n", err) @@ -122,6 +123,14 @@ func main() { fmt.Printf("%s (%s)\n", password, spellPass) continue } + if config.Algorithm == apg.AlgoPronounceable && config.SpellPronounceable { + pronouncePass, err := generator.Pronounce() + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "failed to pronounce password: %s\n", err) + } + fmt.Printf("%s (%s)\n", password, pronouncePass) + continue + } fmt.Println(password) } } @@ -145,6 +154,8 @@ Flags: -m LENGTH Minimum length of the password to be generated (Default: 12) -x LENGTH Maximum length of the password to be generated (Default: 20) -f LENGTH Fixed length of the password to be generated (Ignores -m and -x) + - Note: Due to the way the pronounceable password algorithm works, + this setting might not always apply -n NUMBER Amount of password to be generated (Default: 6) -E CHARS List of characters to be excluded in the generated password -M [LUNSHClunshc] New style password flags @@ -155,6 +166,8 @@ Flags: -mU NUMBER Minimum amount of upper-case characters (implies -U) - Note: any of the "Minimum amount of" modes may result in extraordinarily long calculation times + - Note 2: The "minimum amount of" modes do not apply in + pronounceable mode (-a 0) -C Enable complex password mode (implies -L -U -N -S and disables -H) -H Avoid ambiguous characters in passwords (i. e.: 1, l, I, O, 0) (Default: off) -L Toggle lower-case characters in passwords (Default: on) @@ -163,6 +176,8 @@ Flags: -U Toggle upper-case characters in passwords (Default: on) - Note: this flag has higher priority than the other old-style flags -l Spell generated passwords in phonetic alphabet (Default: off) + -t Spell generated pronounceable passwords with the corresponding + syllables (Default: off) -p Check the HIBP database if the generated passwords was found in a leak before (Default: off) - Note: this feature requires internet connectivity -h Show this help text diff --git a/config.go b/config.go index b5253c9..47c09d7 100644 --- a/config.go +++ b/config.go @@ -43,6 +43,9 @@ type Config struct { NumberPass int64 // SpellPassword if set will spell the generated passwords in the phonetic alphabet SpellPassword bool + // SpellPronounceable if set will spell the generated pronounceable passwords in + // as its corresponding syllables + SpellPronounceable bool } // Option is a function that can override default Config settings diff --git a/config_test.go b/config_test.go index b79b5ed..712d27d 100644 --- a/config_test.go +++ b/config_test.go @@ -35,7 +35,7 @@ func TestWithAlgorithm(t *testing.T) { algo Algorithm want int }{ - {"Pronouncble passwords", AlgoPronouncable, 0}, + {"Pronouncble passwords", AlgoPronounceable, 0}, {"Random passwords", AlgoRandom, 1}, {"Coinflip", AlgoCoinFlip, 2}, {"Unsupported", AlgoUnsupported, 3}, diff --git a/random.go b/random.go index 8e0b665..4fa6df4 100644 --- a/random.go +++ b/random.go @@ -45,8 +45,8 @@ func (g *Generator) CoinFlipBool() bool { // it as string type. If the generation fails, an error will be thrown func (g *Generator) Generate() (string, error) { switch g.config.Algorithm { - case AlgoPronouncable: - return g.generatePronouncable() + case AlgoPronounceable: + return g.generatePronounceable() case AlgoCoinFlip: return g.generateCoinFlip() case AlgoRandom: @@ -284,18 +284,19 @@ func (g *Generator) generateCoinFlip() (string, error) { return "Tails", nil } -// generatePronouncable is executed when Generate() is called with Algorithm set -// to AlgoPronouncable -func (g *Generator) generatePronouncable() (string, error) { +// generatePronounceable is executed when Generate() is called with Algorithm set +// to AlgoPronounceable +func (g *Generator) generatePronounceable() (string, error) { var password string - syllables := make([]string, 0) + g.syllables = make([]string, 0) length, err := g.GetPasswordLength() if err != nil { return "", fmt.Errorf("failed to calculate password length: %w", err) } - characterSet := append(KoremutakeSyllables, strings.Split(CharRangeNumericHuman, "")...) + characterSet := KoremutakeSyllables + characterSet = append(characterSet, strings.Split(CharRangeNumericHuman, "")...) characterSet = append(characterSet, strings.Split(CharRangeSpecialHuman, "")...) characterSetLength := len(characterSet) for int64(len(password)) < length { @@ -316,7 +317,7 @@ func (g *Generator) generatePronouncable() (string, error) { nextSyllable = strings.ReplaceAll(nextSyllable, randomChar, strings.ToUpper(randomChar)) } password += nextSyllable - syllables = append(syllables, nextSyllable) + g.syllables = append(g.syllables, nextSyllable) } return password, nil diff --git a/random_test.go b/random_test.go index 3e99e5a..4e42a24 100644 --- a/random_test.go +++ b/random_test.go @@ -299,14 +299,14 @@ func TestGenerateCoinFlip(t *testing.T) { } } -func TestGeneratePronouncable(t *testing.T) { +func TestGeneratePronounceable(t *testing.T) { config := NewConfig() generator := New(config) foundSylables := 0 for range 100 { - res, err := generator.generatePronouncable() + res, err := generator.generatePronounceable() if err != nil { - t.Errorf("generatePronouncable() failed: %s", err) + t.Errorf("generatePronounceable() failed: %s", err) return } for _, syl := range KoremutakeSyllables { @@ -316,7 +316,7 @@ func TestGeneratePronouncable(t *testing.T) { } } if foundSylables < 100 { - t.Errorf("generatePronouncable() failed, expected at least 1 sylable, got none") + t.Errorf("generatePronounceable() failed, expected at least 1 sylable, got none") } } @@ -440,8 +440,8 @@ func TestGenerate(t *testing.T) { expectedErr error }{ { - name: "Pronouncable", - algorithm: AlgoPronouncable, + name: "Pronounceable", + algorithm: AlgoPronounceable, }, { name: "CoinFlip", diff --git a/spelling.go b/spelling.go index 67b0028..67a835c 100644 --- a/spelling.go +++ b/spelling.go @@ -93,6 +93,31 @@ func Spell(input string) (string, error) { return strings.Join(returnString, "/"), nil } +// Pronounce returns last generated pronounceable password as spelled syllables string +func (g *Generator) Pronounce() (string, error) { + var returnString []string + for _, syllable := range g.syllables { + isKoremutake := false + for _, x := range KoremutakeSyllables { + if x == strings.ToLower(syllable) { + isKoremutake = true + } + } + + if isKoremutake { + returnString = append(returnString, syllable) + continue + } + + curSpellString, err := ConvertByteToWord(syllable[0]) + if err != nil { + return "", err + } + returnString = append(returnString, curSpellString) + } + return strings.Join(returnString, "-"), nil +} + // ConvertByteToWord converts a given ASCII byte into the corresponding spelled version // of the english phonetic alphabet func ConvertByteToWord(charByte byte) (string, error) {