Refactor spelling of "Pronounceable" and introduce syllable spelling feature

The spelling of "Pronounceable" has been adjusted throughout the code. Moreover, a new functionality for producing pronounceable passwords spelled as correlating syllables has been integrated. This includes relevant changes to password character sets used for pronounceable passwords and enhancements to test this new feature.
This commit is contained in:
Winni Neessen 2024-03-12 18:28:01 +01:00
parent 90ff88de41
commit fefb2557fc
Signed by: wneessen
GPG key ID: 5F3AF39B820C119D
9 changed files with 67 additions and 20 deletions

View file

@ -5,9 +5,9 @@ package apg
type Algorithm int type Algorithm int
const ( const (
// AlgoPronouncable represents the algorithm for pronouncable passwords // AlgoPronounceable represents the algorithm for pronounceable passwords
// (koremutake syllables) // (koremutake syllables)
AlgoPronouncable Algorithm = iota AlgoPronounceable Algorithm = iota
// AlgoRandom represents the algorithm for purely random passwords according // AlgoRandom represents the algorithm for purely random passwords according
// to the provided password modes/flags // to the provided password modes/flags
AlgoRandom AlgoRandom
@ -23,7 +23,7 @@ const (
func IntToAlgo(a int) Algorithm { func IntToAlgo(a int) Algorithm {
switch a { switch a {
case 0: case 0:
return AlgoPronouncable return AlgoPronounceable
case 1: case 1:
return AlgoRandom return AlgoRandom
case 2: case 2:

View file

@ -8,7 +8,7 @@ func TestIntToAlgo(t *testing.T) {
a int a int
e Algorithm e Algorithm
}{ }{
{"AlgoPronouncable", 0, AlgoPronouncable}, {"AlgoPronounceable", 0, AlgoPronounceable},
{"AlgoRandom", 1, AlgoRandom}, {"AlgoRandom", 1, AlgoRandom},
{"AlgoCoinflip", 2, AlgoCoinFlip}, {"AlgoCoinflip", 2, AlgoCoinFlip},
{"AlgoUnsupported", 3, AlgoUnsupported}, {"AlgoUnsupported", 3, AlgoUnsupported},

3
apg.go
View file

@ -7,6 +7,9 @@ const VERSION = "2.0.0"
type Generator struct { type Generator struct {
// config is a pointer to the apg config instance // config is a pointer to the apg config instance
config *Config config *Config
// syllables holds the single syllables of the lasst generated
// pronounceable password
syllables []string
} }
// New returns a new password Generator type // New returns a new password Generator type

View file

@ -42,6 +42,7 @@ func main() {
flag.StringVar(&modeString, "M", "", "") flag.StringVar(&modeString, "M", "", "")
flag.Int64Var(&config.NumberPass, "n", config.NumberPass, "") flag.Int64Var(&config.NumberPass, "n", config.NumberPass, "")
flag.BoolVar(&config.SpellPassword, "l", false, "") flag.BoolVar(&config.SpellPassword, "l", false, "")
flag.BoolVar(&config.SpellPronounceable, "t", false, "")
flag.Usage = usage flag.Usage = usage
flag.Parse() flag.Parse()
@ -114,7 +115,7 @@ func main() {
_, _ = fmt.Fprintf(os.Stderr, "failed to generate password: %s\n", err) _, _ = fmt.Fprintf(os.Stderr, "failed to generate password: %s\n", err)
os.Exit(1) os.Exit(1)
} }
if config.SpellPassword { if config.Algorithm == apg.AlgoRandom && config.SpellPassword {
spellPass, err := apg.Spell(password) spellPass, err := apg.Spell(password)
if err != nil { if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "failed to spell password: %s\n", err) _, _ = fmt.Fprintf(os.Stderr, "failed to spell password: %s\n", err)
@ -122,6 +123,14 @@ func main() {
fmt.Printf("%s (%s)\n", password, spellPass) fmt.Printf("%s (%s)\n", password, spellPass)
continue 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) fmt.Println(password)
} }
} }
@ -145,6 +154,8 @@ Flags:
-m LENGTH Minimum length of the password to be generated (Default: 12) -m LENGTH Minimum length of the password to be generated (Default: 12)
-x LENGTH Maximum length of the password to be generated (Default: 20) -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) -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) -n NUMBER Amount of password to be generated (Default: 6)
-E CHARS List of characters to be excluded in the generated password -E CHARS List of characters to be excluded in the generated password
-M [LUNSHClunshc] New style password flags -M [LUNSHClunshc] New style password flags
@ -155,6 +166,8 @@ Flags:
-mU NUMBER Minimum amount of upper-case characters (implies -U) -mU NUMBER Minimum amount of upper-case characters (implies -U)
- Note: any of the "Minimum amount of" modes may result in - Note: any of the "Minimum amount of" modes may result in
extraordinarily long calculation times 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) -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) -H Avoid ambiguous characters in passwords (i. e.: 1, l, I, O, 0) (Default: off)
-L Toggle lower-case characters in passwords (Default: on) -L Toggle lower-case characters in passwords (Default: on)
@ -163,6 +176,8 @@ Flags:
-U Toggle upper-case characters in passwords (Default: on) -U Toggle upper-case characters in passwords (Default: on)
- Note: this flag has higher priority than the other old-style flags - Note: this flag has higher priority than the other old-style flags
-l Spell generated passwords in phonetic alphabet (Default: off) -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) -p Check the HIBP database if the generated passwords was found in a leak before (Default: off)
- Note: this feature requires internet connectivity - Note: this feature requires internet connectivity
-h Show this help text -h Show this help text

View file

@ -43,6 +43,9 @@ type Config struct {
NumberPass int64 NumberPass int64
// SpellPassword if set will spell the generated passwords in the phonetic alphabet // SpellPassword if set will spell the generated passwords in the phonetic alphabet
SpellPassword bool 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 // Option is a function that can override default Config settings

View file

@ -35,7 +35,7 @@ func TestWithAlgorithm(t *testing.T) {
algo Algorithm algo Algorithm
want int want int
}{ }{
{"Pronouncble passwords", AlgoPronouncable, 0}, {"Pronouncble passwords", AlgoPronounceable, 0},
{"Random passwords", AlgoRandom, 1}, {"Random passwords", AlgoRandom, 1},
{"Coinflip", AlgoCoinFlip, 2}, {"Coinflip", AlgoCoinFlip, 2},
{"Unsupported", AlgoUnsupported, 3}, {"Unsupported", AlgoUnsupported, 3},

View file

@ -45,8 +45,8 @@ func (g *Generator) CoinFlipBool() bool {
// it as string type. If the generation fails, an error will be thrown // it as string type. If the generation fails, an error will be thrown
func (g *Generator) Generate() (string, error) { func (g *Generator) Generate() (string, error) {
switch g.config.Algorithm { switch g.config.Algorithm {
case AlgoPronouncable: case AlgoPronounceable:
return g.generatePronouncable() return g.generatePronounceable()
case AlgoCoinFlip: case AlgoCoinFlip:
return g.generateCoinFlip() return g.generateCoinFlip()
case AlgoRandom: case AlgoRandom:
@ -284,18 +284,19 @@ func (g *Generator) generateCoinFlip() (string, error) {
return "Tails", nil return "Tails", nil
} }
// generatePronouncable is executed when Generate() is called with Algorithm set // generatePronounceable is executed when Generate() is called with Algorithm set
// to AlgoPronouncable // to AlgoPronounceable
func (g *Generator) generatePronouncable() (string, error) { func (g *Generator) generatePronounceable() (string, error) {
var password string var password string
syllables := make([]string, 0) g.syllables = make([]string, 0)
length, err := g.GetPasswordLength() length, err := g.GetPasswordLength()
if err != nil { if err != nil {
return "", fmt.Errorf("failed to calculate password length: %w", err) 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, "")...) characterSet = append(characterSet, strings.Split(CharRangeSpecialHuman, "")...)
characterSetLength := len(characterSet) characterSetLength := len(characterSet)
for int64(len(password)) < length { for int64(len(password)) < length {
@ -316,7 +317,7 @@ func (g *Generator) generatePronouncable() (string, error) {
nextSyllable = strings.ReplaceAll(nextSyllable, randomChar, strings.ToUpper(randomChar)) nextSyllable = strings.ReplaceAll(nextSyllable, randomChar, strings.ToUpper(randomChar))
} }
password += nextSyllable password += nextSyllable
syllables = append(syllables, nextSyllable) g.syllables = append(g.syllables, nextSyllable)
} }
return password, nil return password, nil

View file

@ -299,14 +299,14 @@ func TestGenerateCoinFlip(t *testing.T) {
} }
} }
func TestGeneratePronouncable(t *testing.T) { func TestGeneratePronounceable(t *testing.T) {
config := NewConfig() config := NewConfig()
generator := New(config) generator := New(config)
foundSylables := 0 foundSylables := 0
for range 100 { for range 100 {
res, err := generator.generatePronouncable() res, err := generator.generatePronounceable()
if err != nil { if err != nil {
t.Errorf("generatePronouncable() failed: %s", err) t.Errorf("generatePronounceable() failed: %s", err)
return return
} }
for _, syl := range KoremutakeSyllables { for _, syl := range KoremutakeSyllables {
@ -316,7 +316,7 @@ func TestGeneratePronouncable(t *testing.T) {
} }
} }
if foundSylables < 100 { 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 expectedErr error
}{ }{
{ {
name: "Pronouncable", name: "Pronounceable",
algorithm: AlgoPronouncable, algorithm: AlgoPronounceable,
}, },
{ {
name: "CoinFlip", name: "CoinFlip",

View file

@ -93,6 +93,31 @@ func Spell(input string) (string, error) {
return strings.Join(returnString, "/"), nil 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 // ConvertByteToWord converts a given ASCII byte into the corresponding spelled version
// of the english phonetic alphabet // of the english phonetic alphabet
func ConvertByteToWord(charByte byte) (string, error) { func ConvertByteToWord(charByte byte) (string, error) {